Skip to content

Commit

Permalink
Merge pull request pycontribs#318 from olark/add-support-for-service-…
Browse files Browse the repository at this point in the history
…desk

Add support for some Service Desk API calls
  • Loading branch information
ssbarnea authored Feb 27, 2017
2 parents 35d2900 + d6bdf35 commit 693b2f0
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 7 deletions.
165 changes: 159 additions & 6 deletions jira/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,41 @@ def emit(self, record):
from requests.utils import get_netrc_auth
from six import iteritems
from six.moves.urllib.parse import urlparse
# JIRA specific resources
from jira.resources import * # NOQA

# GreenHopper specific resources
from jira.exceptions import JIRAError
from jira.resilientsession import raise_on_error
from jira.resilientsession import ResilientSession
# JIRA specific resources
from jira.resources import Attachment
from jira.resources import Board
from jira.resources import Comment
from jira.resources import Component
from jira.resources import Customer
from jira.resources import CustomFieldOption
from jira.resources import Dashboard
from jira.resources import Filter
from jira.resources import GreenHopperResource
from jira.resources import Issue
from jira.resources import IssueLink
from jira.resources import IssueLinkType
from jira.resources import IssueType
from jira.resources import Priority
from jira.resources import Project
from jira.resources import RemoteLink
from jira.resources import RequestType
from jira.resources import Resolution
from jira.resources import Resource
from jira.resources import Role
from jira.resources import SecurityLevel
from jira.resources import ServiceDesk
from jira.resources import Sprint
from jira.resources import Status
from jira.resources import User
from jira.resources import Version
from jira.resources import Votes
from jira.resources import Watchers
from jira.resources import Worklog

from jira import __version__
from jira.utils import CaseInsensitiveDict
Expand Down Expand Up @@ -954,6 +979,96 @@ def create_issues(self, field_list, prefetch=True):
'error': None, 'input_fields': fields})
return issue_list

def supports_service_desk(self):
url = self._options['server'] + '/rest/servicedeskapi/info'
headers = {'X-ExperimentalApi': 'opt-in'}
try:
r = self._session.get(url, headers=headers)
return r.status_code == 200
except JIRAError:
return False

def create_customer(self, email, displayName):
"""Create a new customer and return an issue Resource for it."""
url = self._options['server'] + '/rest/servicedeskapi/customer'
headers = {'X-ExperimentalApi': 'opt-in'}
r = self._session.post(url, headers=headers, data=json.dumps({
'email': email,
'displayName': displayName
}))

raw_customer_json = json_loads(r)

if r.status_code != 201:
raise JIRAError(r.status_code, request=r)
return Customer(self._options, self._session, raw=raw_customer_json)

def service_desks(self):
"""Get a list of ServiceDesk Resources from the server visible to the current authenticated user."""
url = self._options['server'] + '/rest/servicedeskapi/servicedesk'
headers = {'X-ExperimentalApi': 'opt-in'}
r_json = json_loads(self._session.get(url, headers=headers))
projects = [ServiceDesk(self._options, self._session, raw_project_json)
for raw_project_json in r_json['values']]
return projects

def service_desk(self, id):
"""Get a Service Desk Resource from the server.
:param id: ID or key of the Service Desk to get
"""
return self._find_for_resource(ServiceDesk, id)

def create_customer_request(self, fields=None, prefetch=True, **fieldargs):
"""Create a new customer request and return an issue Resource for it.
Each keyword argument (other than the predefined ones) is treated as a field name and the argument's value
is treated as the intended value for that field -- if the fields argument is used, all other keyword arguments
will be ignored.
By default, the client will immediately reload the issue Resource created by this method in order to return
a complete Issue object to the caller; this behavior can be controlled through the 'prefetch' argument.
JIRA projects may contain many different issue types. Some issue screens have different requirements for
fields in a new issue. This information is available through the 'createmeta' method. Further examples are
available here: https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+Create+Issue
:param fields: a dict containing field names and the values to use. If present, all other keyword arguments
will be ignored
:param prefetch: whether to reload the created issue Resource so that all of its data is present in the value
returned from this method
"""
data = fields

p = data['serviceDeskId']
service_desk = None

if isinstance(p, string_types) or isinstance(p, integer_types):
service_desk = self.service_desk(p)
elif isinstance(p, ServiceDesk):
service_desk = p

data['serviceDeskId'] = service_desk.id

p = data['requestTypeId']
if isinstance(p, integer_types):
data['requestTypeId'] = p
elif isinstance(p, string_types):
data['requestTypeId'] = self.request_type_by_name(
service_desk, p).id

url = self._options['server'] + '/rest/servicedeskapi/request'
headers = {'X-ExperimentalApi': 'opt-in'}
r = self._session.post(url, headers=headers, data=json.dumps(data))

raw_issue_json = json_loads(r)
if 'issueKey' not in raw_issue_json:
raise JIRAError(r.status_code, request=r)
if prefetch:
return self.issue(raw_issue_json['issueKey'])
else:
return Issue(self._options, self._session, raw=raw_issue_json)

def createmeta(self, projectKeys=None, projectIds=[], issuetypeIds=None, issuetypeNames=None, expand=None):
"""Get the metadata required to create issues, optionally filtered by projects and issue types.
Expand Down Expand Up @@ -1025,7 +1140,7 @@ def comment(self, issue, comment):
return self._find_for_resource(Comment, (issue, comment))

@translate_resource_args
def add_comment(self, issue, body, visibility=None):
def add_comment(self, issue, body, visibility=None, is_internal=False):
"""Add a comment from the current authenticated user on the specified issue and return a Resource for it.
The issue identifier and comment body are required.
Expand All @@ -1036,15 +1151,27 @@ def add_comment(self, issue, body, visibility=None):
"type" is 'role' (or 'group' if the JIRA server has configured
comment visibility for groups) and 'value' is the name of the role
(or group) to which viewing of this comment will be restricted.
:param is_internal: defines whether a comment has to be marked as 'Internal' in Jira Service Desk
"""
data = {
'body': body}
'body': body,
}

if is_internal:
data.update({
'properties': [
{'key': 'sd.public.comment',
'value': {'internal': is_internal}}
]
})

if visibility is not None:
data['visibility'] = visibility

url = self._get_url('issue/' + str(issue) + '/comment')
r = self._session.post(
url, data=json.dumps(data))
url, data=json.dumps(data)
)

comment = Comment(self._options, self._session, raw=json_loads(r))
return comment
Expand Down Expand Up @@ -1487,6 +1614,27 @@ def issue_type_by_name(self, name):
raise KeyError("Issue type '%s' is unknown." % name)
return issue_type

def request_types(self, service_desk):
if hasattr(service_desk, 'id'):
service_desk = service_desk.id
url = (self._options['server'] +
'/rest/servicedeskapi/servicedesk/%s/requesttype'
% service_desk)
headers = {'X-ExperimentalApi': 'opt-in'}
r_json = json_loads(self._session.get(url, headers=headers))
request_types = [
RequestType(self._options, self._session, raw_type_json)
for raw_type_json in r_json['values']]
return request_types

def request_type_by_name(self, service_desk, name):
request_types = self.request_types(service_desk)
try:
request_type = [rt for rt in request_types if rt.name == name][0]
except IndexError:
raise KeyError("Request type '%s' is unknown." % name)
return request_type

# User permissions

# non-resource
Expand Down Expand Up @@ -2586,12 +2734,17 @@ def create_project(self, key, name=None, assignee=None, type="Software", templat
r = self._session.get(url)
j = json_loads(r)

possible_templates = ['JIRA Classic', 'JIRA Default Schemes', 'Basic software development']

if template_name is not None:
possible_templates = [template_name]

# https://confluence.atlassian.com/jirakb/creating-a-project-via-rest-based-on-jira-default-schemes-744325852.html
template_key = 'com.atlassian.jira-legacy-project-templates:jira-blank-item'
templates = []
for template in _get_template_list(j):
templates.append(template['name'])
if template['name'] in ['JIRA Classic', 'JIRA Default Schemes', 'Basic software development', template_name]:
if template['name'] in possible_templates:
template_key = template['projectTemplateModuleCompleteKey']
break

Expand Down
33 changes: 32 additions & 1 deletion jira/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def emit(self, record):
'Status',
'User',
'CustomFieldOption',
'RemoteLink'
'RemoteLink',
'Customer',
'ServiceDesk',
'RequestType',
)

logging.getLogger('jira').addHandler(NullHandler())
Expand Down Expand Up @@ -825,6 +828,34 @@ def delete(self, params=None):
Resource.delete(self, params)


# Service Desk

class Customer(Resource):
"""A Service Desk customer."""

def __init__(self, options, session, raw=None):
Resource.__init__(self, 'customer', options, session, '{server}/rest/servicedeskapi/{path}')
if raw:
self._parse_raw(raw)


class ServiceDesk(Resource):
"""A Service Desk."""

def __init__(self, options, session, raw=None):
Resource.__init__(self, 'servicedesk/{0}', options, session, '{server}/rest/servicedeskapi/{path}')
if raw:
self._parse_raw(raw)


class RequestType(Resource):
"""A Service Desk Request Type."""

def __init__(self, options, session, raw=None):
Resource.__init__(self, 'servicedesk/{0}/requesttype', options, session, '{server}/rest/servicedeskapi/{path}')
if raw:
self._parse_raw(raw)

# Utilities


Expand Down
30 changes: 30 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2104,6 +2104,36 @@ def test_jirashell_command_exists(self):
self.assertEqual(result, 0)


class JiraServiceDeskTests(unittest.TestCase):

def setUp(self):
self.jira = JiraTestManager().jira_admin
self.test_manager = JiraTestManager()

def test_create_customer_request(self):
if not self.jira.supports_service_desk():
pytest.skip('Skipping Service Desk not enabled')

try:
self.jira.create_project('TESTSD', template_name='IT Service Desk')
except JIRAError:
pass
service_desk = self.jira.service_desks()[0]
request_type = self.jira.request_types(service_desk)[0]

request = self.jira.create_customer_request(dict(
serviceDeskId=service_desk.id,
requestTypeId=int(request_type.id),
requestFieldValues=dict(
summary='Ticket title here',
description='Ticket body here'
)
))

self.assertEqual(request.fields.summary, 'Ticket title here')
self.assertEqual(request.fields.description, 'Ticket body here')


if __name__ == '__main__':

# when running tests we expect various errors and we don't want to display them by default
Expand Down

0 comments on commit 693b2f0

Please sign in to comment.