forked from OCA/connector-jira
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Map projects by external_id + set of jira orgs Project bindings now can be assigned to one or more jira organizations. The binding for the project accept an additional argument for organizations. A task will be linked with the project having the exact same set of organizations that it has, or fallback to a project without organization. A constraint ensures that you cannot have several projects with the same set of organizations or 2 projects without organization. The link wizard has a new step to select the organization. The REST API for Serviced Desk is a different one. The former code was based on pycontribs/jira#388 which is closed and unmaintained. We only need to read the organizations from the servicedesk REST API and the local code is minimal. We can now use the normal jira library.
- Loading branch information
Showing
21 changed files
with
561 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
{ | ||
'name': 'JIRA Connector ServiceDesk', | ||
'version': '11.0.1.0.0', | ||
'author': 'Camptocamp,Odoo Community Association (OCA)', | ||
'license': 'AGPL-3', | ||
'category': 'Connector', | ||
'depends': [ | ||
'connector_jira', | ||
], | ||
'website': 'https://www.camptocamp.com', | ||
'data': [ | ||
'views/jira_backend_views.xml', | ||
'views/project_project_views.xml', | ||
'views/project_link_jira_views.xml', | ||
'security/ir.model.access.csv', | ||
], | ||
'installable': True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from . import account_analytic_line | ||
from . import jira_backend | ||
from . import project_project | ||
from . import jira_organization | ||
from . import project_task |
1 change: 1 addition & 0 deletions
1
connector_jira_servicedesk/models/account_analytic_line/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import importer |
16 changes: 16 additions & 0 deletions
16
connector_jira_servicedesk/models/account_analytic_line/importer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
from odoo.addons.component.core import Component | ||
|
||
|
||
class AnalyticLineImporter(Component): | ||
_inherit = 'jira.analytic.line.importer' | ||
|
||
@property | ||
def _issue_fields_to_read(self): | ||
issue_fields = super()._issue_fields_to_read | ||
organization_field_name = self.backend_record.organization_field_name | ||
if not organization_field_name: | ||
return issue_fields | ||
return issue_fields + [organization_field_name] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import common |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Copyright 2016 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
from odoo import models, api, fields | ||
|
||
|
||
class JiraBackend(models.Model): | ||
_inherit = 'jira.backend' | ||
|
||
organization_ids = fields.One2many( | ||
comodel_name='jira.organization', | ||
inverse_name='backend_id', | ||
string='Organizations', | ||
readonly=True, | ||
) | ||
|
||
organization_field_name = fields.Char( | ||
string='Organization Field', | ||
help="The 'Organization' field on JIRA is a custom field. " | ||
"The name of the field is something like 'customfield_10002'. " | ||
) | ||
|
||
@api.model | ||
def _selection_project_template(self): | ||
selection = super()._selection_project_template() | ||
selection += [ | ||
('Basic', 'Basic (Service Desk)'), | ||
('IT Service Desk', 'IT Service Desk (Service Desk)'), | ||
('Customer service', 'Customer Service (Service Desk)'), | ||
] | ||
return selection | ||
|
||
@api.multi | ||
def import_organization(self): | ||
self.env['jira.organization'].import_batch(self) | ||
return True | ||
|
||
@api.multi | ||
def activate_organization(self): | ||
"""Get organization field name from JIRA web-service""" | ||
self.ensure_one() | ||
org_field = 'com.atlassian.servicedesk:sd-customer-organizations' | ||
with self.work_on('jira.backend') as work: | ||
adapter = work.component(usage='backend.adapter') | ||
jira_fields = adapter.list_fields() | ||
for field in jira_fields: | ||
custom_ref = field.get('schema', {}).get('custom') | ||
if custom_ref == org_field: | ||
self.organization_field_name = field['id'] | ||
break |
3 changes: 3 additions & 0 deletions
3
connector_jira_servicedesk/models/jira_organization/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from . import common | ||
from . import importer | ||
from . import adapter |
61 changes: 61 additions & 0 deletions
61
connector_jira_servicedesk/models/jira_organization/adapter.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Copyright 2018 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
import jira | ||
from jira.utils import CaseInsensitiveDict | ||
|
||
from odoo.addons.component.core import Component | ||
|
||
|
||
class Organization(jira.resources.Resource): | ||
"""A Service Desk Organization.""" | ||
|
||
def __init__(self, options, session, raw=None): | ||
super().__init__( | ||
'organization/{0}', | ||
options, | ||
session, | ||
'{server}/rest/servicedeskapi/{path}' | ||
) | ||
if raw: | ||
self._parse_raw(raw) | ||
|
||
|
||
class OrganizationAdapter(Component): | ||
_name = 'jira.organization.adapter' | ||
_inherit = ['jira.webservice.adapter'] | ||
_apply_on = ['jira.organization'] | ||
|
||
# The Service Desk REST API returns an error if this header | ||
# is not used. The API may change so they want an agreement for | ||
# the client about this. | ||
_desk_headers = CaseInsensitiveDict({'X-ExperimentalApi': 'opt-in'}) | ||
|
||
def __init__(self, work_context): | ||
super().__init__(work_context) | ||
self.client._session.headers.update(self._desk_headers) | ||
|
||
def read(self, id_): | ||
organization = Organization( | ||
self.client._options, | ||
self.client._session | ||
) | ||
organization.find(id_) | ||
return organization.raw | ||
|
||
def search(self): | ||
base = (self.client._options['server'] + | ||
'/rest/servicedeskapi/organization') | ||
# By default, a GET on the REST API returns only one page with the | ||
# first 50 rows. Here, client is an instance of the jira library's JIRA | ||
# class, which provides a _fetch_pages method to fetch pages. | ||
# maxResults=False means it will try to get all pages. | ||
orgs = self.client._fetch_pages( | ||
Organization, | ||
'values', | ||
'organization', | ||
# limit to False will get them in batch | ||
maxResults=False, | ||
base=base | ||
) | ||
return [org.id for org in orgs] |
30 changes: 30 additions & 0 deletions
30
connector_jira_servicedesk/models/jira_organization/common.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Copyright 2016 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
|
||
from odoo import fields, models | ||
from odoo.addons.queue_job.job import job | ||
|
||
|
||
class JiraOrganization(models.Model): | ||
_name = 'jira.organization' | ||
_inherit = 'jira.binding' | ||
_description = 'Jira Organization' | ||
|
||
name = fields.Char('Name', required=True, readonly=True) | ||
backend_id = fields.Many2one( | ||
ondelete='cascade' | ||
) | ||
project_ids = fields.Many2many( | ||
comodel_name='jira.project.project' | ||
) | ||
|
||
@job(default_channel='root.connector_jira.import') | ||
def import_batch(self, backend, from_date=None, to_date=None): | ||
""" Prepare a batch import of organization from Jira | ||
from_date and to_date are ignored for organization | ||
""" | ||
with backend.work_on(self._name) as work: | ||
importer = work.component(usage='batch.importer') | ||
importer.run() |
36 changes: 36 additions & 0 deletions
36
connector_jira_servicedesk/models/jira_organization/importer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Copyright 2016 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
from odoo.addons.connector.components.mapper import mapping | ||
|
||
from odoo.addons.component.core import Component | ||
|
||
|
||
class OrganizationMapper(Component): | ||
_name = 'jira.organization.mapper' | ||
_inherit = ['base.import.mapper'] | ||
_apply_on = 'jira.organization' | ||
|
||
direct = [ | ||
('name', 'name'), | ||
] | ||
|
||
@mapping | ||
def backend_id(self, record): | ||
return {'backend_id': self.backend_record.id} | ||
|
||
|
||
class OrganizationBatchImporter(Component): | ||
""" Import the Jira Organizations | ||
For every id in in the list of organizations, a direct import is done. | ||
""" | ||
_name = 'jira.organization.batch.importer' | ||
_inherit = 'jira.direct.batch.importer' | ||
_apply_on = ['jira.organization'] | ||
|
||
def run(self): | ||
""" Run the synchronization """ | ||
record_ids = self.backend_adapter.search() | ||
for record_id in record_ids: | ||
self._import_record(record_id) |
3 changes: 3 additions & 0 deletions
3
connector_jira_servicedesk/models/project_project/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from . import common | ||
from . import binder | ||
from . import project_link_jira |
74 changes: 74 additions & 0 deletions
74
connector_jira_servicedesk/models/project_project/binder.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Copyright 2016-2018 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
import logging | ||
|
||
from odoo import tools | ||
from odoo.addons.component.core import Component | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class JiraProjectBinder(Component): | ||
_name = 'jira.project.binder' | ||
_inherit = 'jira.binder' | ||
|
||
_apply_on = [ | ||
'jira.project.project', | ||
] | ||
|
||
def to_internal(self, external_id, unwrap=False, organizations=None): | ||
""" Give the Odoo recordset for an external ID | ||
When organizations are passed (ids are odoo ids), the binder | ||
will return: | ||
* a project linked with JIRA with the exact set of organizations | ||
* if no project has the exact same set, a project linked without | ||
organization set on the binding | ||
If no organizations are passed, only project bindings | ||
without organization match. | ||
:param external_id: external ID for which we want | ||
the Odoo ID | ||
:param unwrap: if True, returns the normal record | ||
else return the binding record | ||
:param organizations: jira.organization recordset | ||
:return: a recordset, depending on the value of unwrap, | ||
or an empty recordset if the external_id is not mapped | ||
:rtype: recordset | ||
""" | ||
domain = [ | ||
(self._external_field, '=', tools.ustr(external_id)), | ||
(self._backend_field, '=', self.backend_record.id), | ||
] | ||
if not organizations: | ||
domain.append( | ||
('organization_ids', '=', False), | ||
) | ||
candidates = self.model.with_context(active_test=False).search(domain) | ||
if organizations: | ||
fallback = self.model.browse() | ||
binding = self.model.browse() | ||
for candidate in candidates: | ||
if not candidate.organization_ids: | ||
fallback = candidate | ||
continue | ||
|
||
if candidate.organization_ids == organizations: | ||
binding = candidate | ||
break | ||
if not binding: | ||
binding = fallback | ||
else: | ||
binding = candidates | ||
|
||
if not binding: | ||
if unwrap: | ||
return self.model.browse()[self._odoo_field] | ||
return self.model.browse() | ||
binding.ensure_one() | ||
if unwrap: | ||
binding = binding[self._odoo_field] | ||
return binding |
49 changes: 49 additions & 0 deletions
49
connector_jira_servicedesk/models/project_project/common.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Copyright 2019 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
from odoo import api, fields, models, exceptions, _ | ||
|
||
|
||
class JiraProjectBaseFields(models.AbstractModel): | ||
"""JIRA Project Base fields | ||
Shared by the binding jira.project.project | ||
and the wizard to link/create a JIRA project | ||
""" | ||
_inherit = 'jira.project.base.mixin' | ||
|
||
organization_ids = fields.Many2many( | ||
comodel_name='jira.organization', | ||
string='Organization(s) on Jira', | ||
domain="[('backend_id', '=', backend_id)]", | ||
help="If organizations are set, a task will be " | ||
"added to the project only if the project AND " | ||
"the organization match with the selection." | ||
) | ||
|
||
|
||
class JiraProjectProject(models.Model): | ||
_inherit = 'jira.project.project' | ||
|
||
@api.constrains('backend_id', 'external_id', 'organization_ids') | ||
@api.multi | ||
def _constrains_jira_uniq(self): | ||
for binding in self: | ||
same_link_bindings = self.search([ | ||
('id', '!=', self.id), | ||
('backend_id', '=', self.backend_id.id), | ||
('external_id', '=', self.external_id), | ||
]) | ||
for other in same_link_bindings: | ||
my_orgs = binding.organization_ids | ||
other_orgs = other.organization_ids | ||
if not my_orgs and not other_orgs: | ||
raise exceptions.ValidationError(_( | ||
"The project %s is already linked with the same" | ||
" JIRA project without organization." | ||
) % (other.display_name)) | ||
if set(my_orgs.ids) == set(other_orgs.ids): | ||
raise exceptions.ValidationError(_( | ||
"The project %s is already linked with this " | ||
"JIRA project and similar organizations." | ||
) % (other.display_name)) |
34 changes: 34 additions & 0 deletions
34
connector_jira_servicedesk/models/project_project/project_link_jira.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Copyright 2018 Camptocamp SA | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) | ||
|
||
import logging | ||
|
||
from odoo import api, models | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
|
||
class ProjectLinkJira(models.TransientModel): | ||
_inherit = 'project.link.jira' | ||
|
||
@api.model | ||
def _selection_state(self): | ||
states = super()._selection_state() | ||
states.append(('link_organizations', 'Link Organizations')) | ||
return states | ||
|
||
def state_exit_start(self): | ||
if self.sync_action == 'link': | ||
self.state = 'link_organizations' | ||
else: | ||
super().state_exit_start() | ||
|
||
def state_exit_link_organizations(self): | ||
if not self.jira_project_id: | ||
self._link_binding() | ||
self.state = 'issue_types' | ||
|
||
def _prepare_link_binding_values(self, jira_project): | ||
values = super()._prepare_link_binding_values(jira_project) | ||
values['organization_ids'] = [(6, 0, self.organization_ids.ids)] | ||
return values |
Oops, something went wrong.