Browse Source

APR 15: [FIX] Bug fixed 'odoo_jira_connector'

pull/331/merge
Cybrosys Technologies 2 weeks ago
parent
commit
5770d9f7d2
  1. 2
      odoo_jira_connector/__manifest__.py
  2. 5
      odoo_jira_connector/doc/RELEASE_NOTES.md
  3. 85
      odoo_jira_connector/models/mail_message.py
  4. 222
      odoo_jira_connector/models/res_config_settings.py

2
odoo_jira_connector/__manifest__.py

@ -21,7 +21,7 @@
##############################################################################
{
'name': 'Odoo Jira Connector',
'version': '17.0.1.0.1',
'version': '17.0.1.0.2',
'category': 'Project',
'summary': 'Odoo Jira Connector is a valuable integration tool for '
'businesses that use both Odoo and Jira. By connecting these '

5
odoo_jira_connector/doc/RELEASE_NOTES.md

@ -9,3 +9,8 @@
#### Version 17.0.1.0.1
##### BUGFIX
- Updated the function for importing data from Jira.
#### 11.04.2025
#### Version 17.0.1.0.2
##### BUGFIX
- Fixed issue when importing comments with attachments from Jira.

85
odoo_jira_connector/models/mail_message.py

@ -21,9 +21,11 @@
##############################################################################
import json
import requests
import logging
from odoo import api, fields, models
from odoo.tools import html2plaintext
_logger = logging.getLogger(__name__)
class MailMessage(models.Model):
"""
@ -31,7 +33,7 @@ class MailMessage(models.Model):
override the create function
Methods:
create(values_list):
extends create() to create comment in Jira
Extends create() to create comments in Jira, handling text and attachments
"""
_inherit = 'mail.message'
@ -40,32 +42,56 @@ class MailMessage(models.Model):
@api.model_create_multi
def create(self, values_list):
""" For creating comment in Jira and comments in the chatter """
message = super(MailMessage, self).create(values_list)
if message.message_id_jira == 0:
""" For creating comments in Jira and comments in the chatter """
messages = super(MailMessage, self).create(values_list)
ir_config_parameter = self.env['ir.config_parameter'].sudo()
if ir_config_parameter.get_param('odoo_jira_connector.connection'):
if not ir_config_parameter.get_param('odoo_jira_connector.connection'):
return messages
url = ir_config_parameter.get_param('odoo_jira_connector.url')
user = ir_config_parameter.get_param(
'odoo_jira_connector.user_id_jira')
password = ir_config_parameter.get_param(
'odoo_jira_connector.api_token')
if message.model == 'project.task':
task = self.env['project.task'].browse(message.res_id)
current_message = str(html2plaintext(message.body))
response = requests.get(
f'{url}rest/api/3/issue/{task.task_id_jira}/comment',
user = ir_config_parameter.get_param('odoo_jira_connector.user_id_jira')
password = ir_config_parameter.get_param('odoo_jira_connector.api_token')
headers = {
'Accept': 'application/json',
'Content-Type': 'application/json'},
auth=(user, password))
'Content-Type': 'application/json'
}
for message in messages:
if message.message_id_jira == 0 and message.model == 'project.task':
task = self.env['project.task'].browse(message.res_id)
if not task.task_id_jira:
continue
# Extract text content from the message body
current_message = str(html2plaintext(message.body)).strip()
comment_url = f'{url}rest/api/3/issue/{task.task_id_jira}/comment'
# Fetch existing Jira comments to avoid duplicates
try:
response = requests.get(comment_url, headers=headers, auth=(user, password))
response.raise_for_status()
data = response.json()
if response.status_code == 200:
list_of_comments_jira = [
str(comments['body']['content'][0]['content'][0][
'text']) for comments in data['comments']]
if current_message not in list(
filter(None, list_of_comments_jira)):
except requests.RequestException as e:
_logger.error(f"Failed to fetch Jira comments for task {task.task_id_jira}: {str(e)}")
continue
# Parse Jira comments to extract text content
list_of_comments_jira = []
for comment in data.get('comments', []):
text_content = ""
comment_body = comment.get('body', {}).get('content', [])
for content_block in comment_body:
if content_block.get('type') == 'paragraph':
for content_item in content_block.get('content', []):
if content_item.get('type') == 'text':
text_content += content_item.get('text', '') + " "
elif content_item.get('type') == 'emoji':
text_content += content_item.get('attrs', {}).get('text', '') + " "
if text_content.strip():
list_of_comments_jira.append(text_content.strip())
# Post comment to Jira if it's new
if current_message and current_message not in list_of_comments_jira:
data = json.dumps({
'body': {
'type': 'doc',
@ -79,12 +105,13 @@ class MailMessage(models.Model):
}]
}
})
response = requests.post(
url + 'rest/api/3/issue/%s/comment' % (
task.task_id_jira), headers={
'Accept': 'application/json',
'Content-Type': 'application/json'},
data=data, auth=(user, password))
try:
response = requests.post(comment_url, headers=headers, data=data,
auth=(user, password))
response.raise_for_status()
data = response.json()
message.write({'message_id_jira': data.get('id')})
return message
except requests.RequestException as e:
_logger.error(f"Failed to post comment to Jira for task {task.task_id_jira}: {str(e)}")
return messages

222
odoo_jira_connector/models/res_config_settings.py

@ -26,9 +26,12 @@ import re
import requests
from requests.auth import HTTPBasicAuth
from odoo import fields, models, _
import logging
from odoo.exceptions import ValidationError
from odoo.tools import html2plaintext
_logger = logging.getLogger(__name__)
# The Header parameters
HEADERS = {
'Accept': 'application/json',
@ -141,7 +144,7 @@ class ResConfigSettings(models.TransientModel):
}
}
}
except Exception:
except Exception as e:
raise ValidationError(_('Please Enter Valid Credentials.'))
def action_export_to_jira(self):
@ -461,102 +464,171 @@ class ResConfigSettings(models.TransientModel):
return ''
def action_import_from_jira(self):
""" Import all the projects and corresponding tasks
from Odoo to Jira. If a project or task is modified in Odoo,
it will also be updated in Jira.
""" Import projects, tasks, comments, and attachments from Jira to Odoo.
Updates existing tasks if modified in Jira. Handles text, emojis, and image attachments without duplication.
"""
auth = HTTPBasicAuth(self.user_id_jira, self.api_token)
response = requests.get(self.url + 'rest/api/2/project',
# Fetch Jira projects
response = requests.get(f"{self.url}rest/api/2/project",
headers=JIRA_HEADERS, auth=auth)
projects = json.loads(response.text)
odoo_projects = self.env['project.project'].search([])
response.raise_for_status()
projects = response.json()
jira_project_ids = [int(a_dict['id']) for a_dict in projects]
name_list = [a_dict['name'] for a_dict in projects]
key_list = [a_dict['key'] for a_dict in projects]
odoo_projects = self.env['project.project'].search([])
odoo_project_map = {p.project_id_jira: p.id for p in odoo_projects}
for (name, key, jira_id) in zip(name_list, key_list, jira_project_ids):
if jira_id in [project.project_id_jira for project in
odoo_projects]:
response = requests.get(self.url + 'rest/api/3/search',
headers=JIRA_HEADERS,
params={'jql': 'project = %s' % key},
auth=auth)
data = response.json()
project = self.env['project.project'].search(
[('project_id_jira', '=', jira_id)], limit=1)
tasks = self.env['project.task'].search(
[('project_id', '=', project.id)])
task_jira_ids = [task.task_id_jira for task in tasks]
for issue in data['issues']:
comment_url = self.url + 'rest/api/3/issue/%s/comment' % \
issue['key']
if issue['key'] in task_jira_ids:
task = self.env['project.task'].search(
[('task_id_jira', '=', issue['key'])], limit=1)
else:
task = self.env['project.task'].create({
'project_id': project.id,
'name': issue['fields']['summary'],
'task_id_jira': issue['key']
})
self.import_task_count += 10
for project_data in projects:
jira_id = int(project_data['id'])
name = project_data['name']
key = project_data['key']
response = requests.get(comment_url, headers=JIRA_HEADERS,
auth=auth)
data = response.json()
messages = self.env['mail.message'].search(
[('res_id', '=', task.id),
('model', '=', 'project.task')])
odoo_comment_list = [str(html2plaintext(chat.body)) for chat
in messages]
jira_comment_list = [
str(comment['body']['content'][0]['content'][0]['text'])
for comment in data['comments'] if str(
comment['body']['content'][0]['content'][0][
'text']) not in odoo_comment_list]
comment_list = list(filter(None, jira_comment_list))
for comment in comment_list:
task.message_post(body=comment)
# Create or get Odoo project
if jira_id in odoo_project_map:
project_id = odoo_project_map[jira_id]
project = self.env['project.project'].browse(project_id)
else:
project = self.env['project.project'].create({
'name': name,
'project_id_jira': jira_id,
'jira_project_key': key
})
self.import_project_count = 10
response = requests.get(self.url + 'rest/api/3/search',
self.import_project_count += 1
odoo_project_map[jira_id] = project.id
# Fetch issues for the project
response = requests.get(
f"{self.url}rest/api/3/search",
headers=JIRA_HEADERS,
params={'jql': 'project = %s' % key},
auth=auth)
data = response.json()
params={'jql': f'project = {key}'},
auth=auth
)
response.raise_for_status()
issues = response.json().get('issues', [])
odoo_tasks = self.env['project.task'].search(
[('project_id', '=', project.id)])
task_jira_ids = {task.task_id_jira: task.id for task in odoo_tasks}
for issue in issues:
issue_key = issue['key']
for issue in data['issues']:
comment_url = self.url + 'rest/api/3/issue/%s/comment' % \
issue['key']
# Create or get Odoo task
if issue_key in task_jira_ids:
task = self.env['project.task'].browse(
task_jira_ids[issue_key])
else:
task = self.env['project.task'].create({
'project_id': project.id,
'name': issue['fields']['summary'],
'task_id_jira': issue['key']
'task_id_jira': issue_key
})
self.import_task_count += 1
# Fetch issue attachments
attachment_url = f"{self.url}rest/api/3/issue/{issue_key}?fields=attachment"
try:
attachment_response = requests.get(attachment_url,
headers=JIRA_HEADERS,
auth=auth)
attachment_response.raise_for_status()
issue_attachments = {att['filename']: att for att in
attachment_response.json().get(
'fields', {}).get('attachment',
[])}
except requests.RequestException as e:
_logger.error(
f"Failed to fetch attachments for issue {issue_key}: {str(e)}")
issue_attachments = {}
# Fetch comments
comment_url = f"{self.url}rest/api/3/issue/{issue_key}/comment"
response = requests.get(comment_url, headers=JIRA_HEADERS,
auth=auth)
data = response.json()
response.raise_for_status()
comments = response.json().get('comments', [])
# Get existing Odoo comments and attachments to avoid duplicates
messages = self.env['mail.message'].search(
[('res_id', '=', task.id),
('model', '=', 'project.task')])
odoo_comment_list = [str(html2plaintext(chat.body)) for chat
in messages]
jira_comment_list = [
str(comment['body']['content'][0]['content'][0]['text'])
for comment in data['comments'] if str(
comment['body']['content'][0]['content'][0][
'text']) not in odoo_comment_list]
comment_list = list(filter(None, jira_comment_list))
[('res_id', '=', task.id), ('model', '=', 'project.task')])
odoo_comment_ids = {
m.message_id_jira: html2plaintext(m.body).strip() for m in
messages if m.message_id_jira}
existing_attachments = self.env['ir.attachment'].search([
('res_model', '=', 'project.task'),
('res_id', '=', task.id)
])
existing_attachment_names = {att.name for att in
existing_attachments}
for comment in comment_list:
task.message_post(body=comment)
for comment in comments:
jira_comment_id = int(comment['id'])
text_content = ""
attachments = []
comment_body = comment.get('body', {}).get('content', [])
# Process comment content
for block in comment_body:
if block.get('type') == 'paragraph':
for item in block.get('content', []):
if item.get('type') == 'text':
text_content += item.get('text', '') + " "
elif item.get('type') == 'emoji':
text_content += item.get('attrs', {}).get(
'text', '') + " "
elif block.get('type') == 'mediaSingle':
for item in block.get('content', []):
if item.get('type') == 'media':
media_attrs = item.get('attrs', {})
media_id = media_attrs.get('id')
filename = media_attrs.get('alt',
f"attachment-{media_id}")
if filename in issue_attachments:
attachment = issue_attachments[filename]
if attachment[
'filename'] not in existing_attachment_names:
attachments.append({
'url': attachment['content'],
'filename': attachment[
'filename'],
'media_id': media_id
})
else:
_logger.warning(
f"Attachment '{filename}' (media_id: {media_id}) not found for issue {issue_key}")
# Import text comment if not already in Odoo
text_content = text_content.strip()
if text_content and jira_comment_id not in odoo_comment_ids:
message = task.message_post(body=text_content)
message.write({'message_id_jira': jira_comment_id})
odoo_comment_ids[jira_comment_id] = text_content
# Import attachments
for attachment in attachments:
try:
response = requests.get(attachment['url'],
headers=JIRA_HEADERS,
auth=auth, stream=True)
response.raise_for_status()
attachment_data = base64.b64encode(
response.content).decode('utf-8')
self.env['ir.attachment'].create({
'name': attachment['filename'],
'datas': attachment_data,
'res_model': 'project.task',
'res_id': task.id,
'mimetype': self._guess_mimetype(
attachment['filename']),
})
existing_attachment_names.add(
attachment['filename'])
except requests.RequestException as e:
_logger.error(
f"Failed to download attachment '{attachment['filename']}' (media_id: {attachment['media_id']}, URL: {attachment['url']}) for task {task.task_id_jira}: {str(e)}")
def _guess_mimetype(self, filename):
""" Guess the MIME type based on the file extension """
from mimetypes import guess_type
mime_type, _ = guess_type(filename)
return mime_type or 'application/octet-stream'

Loading…
Cancel
Save