Skip to content

Commit

Permalink
ADD get_ticket_content() method to rt.py
Browse files Browse the repository at this point in the history
- Add get_ticket_content() method for RTTicket
- Add _convert_string() method for rt
- Add documentation updates
  • Loading branch information
mjagelka authored and dmranck committed Oct 25, 2018
1 parent e7fa7fa commit df114f1
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 26 deletions.
2 changes: 1 addition & 1 deletion read-the-docs/source/Usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ URL of the ticket. An example is below.

.. note::

For ServiceNow, Jira, Bugzilla and Redmine the user-accessible methods return a ``ticket_content``
For all ticketing tools the user-accessible methods return a ``ticket_content``
field, which contains a json representation of the current ticket's content.
Access this data with ``t.ticket_content``.
25 changes: 24 additions & 1 deletion read-the-docs/source/rt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,33 @@ https://rt-wiki.bestpractical.com/wiki/REST
Methods
^^^^^^^

- `get_ticket_content() <#get_ticket_content>`__
- `create() <#create>`__
- `edit() <#edit>`__
- `add_comment() <#comment>`__
- `change_status() <#status>`__
- `add_attachment() <#add_attachment>`__


get_ticket_content()
--------------------

``get_ticket_content(self, ticket_id=None, option='show')``

Queries the RT API to get the ticket_content using ticket_id. Calls
have different options ('show', 'comment', 'attachments', 'history')
in dependence of what kind of content is required. The ticket_content
is expressed as a dictionary for options 'show', 'attachments' and
'history' and as a list of strings representing lines in returned text
for option 'comment'. The API calling is described in
https://rt-wiki.bestpractical.com/wiki/REST#Ticket

.. code:: python
t = ticket.get_ticket_content(<ticket_id>, option='attachments')
returned_ticket_content = t.ticket_content
create()
--------

Expand Down Expand Up @@ -211,10 +231,13 @@ Update existing RT tickets
t = ticket.edit(priority='4',
cc='username@mail.com')
# Check the ticket content.
t = ticket.get_ticket_id()
returned_ticket_content = t.ticket_content
# Work with a different ticket.
t = ticket.set_ticket_id(<new_ticket_id>)
t = ticket.change_status('Resolved')
# Close Requests session.
ticket.close_requests_session()
40 changes: 29 additions & 11 deletions tests/test_rt.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,23 +153,32 @@ def test_verify_project(self, mock_session):
self.assertTrue(ticket._verify_project(PROJECT))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_verify_ticket_id_unexpected_response(self, mock_session):
def test_get_ticket_content_no_id(self, mock_session):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT)
self.assertEqual(ticket.get_ticket_content(), FAILURE_RESULT)

@patch.object(rt.RTTicket, '_create_requests_session')
def test_get_ticket_content_unexpected_response(self, mock_session):
mock_session.return_value = FakeSession(status_code=404)
error_message = "Error getting ticket content"
with patch.object(rt.RTTicket, '_verify_project'):
ticket = rt.RTTicket(URL, PROJECT)
self.assertFalse(ticket._verify_ticket_id(TICKET_ID))
self.assertEqual(ticket.get_ticket_content(TICKET_ID), FAILURE_RESULT._replace(error_message=error_message))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_verify_ticket_id_not_valid(self, mock_session):
def test_get_ticket_content_id_not_valid(self, mock_session):
mock_session.return_value = FakeSession(status_code=400)
error_message = "Ticket {0} is not valid".format(TICKET_ID)
ticket = rt.RTTicket(URL, PROJECT)
self.assertFalse(ticket._verify_ticket_id(TICKET_ID))
self.assertEqual(ticket.get_ticket_content(TICKET_ID), FAILURE_RESULT._replace(error_message=error_message))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_verify_ticket_id(self, mock_session):
def test_get_ticket_content(self, mock_session):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT)
self.assertTrue(ticket._verify_ticket_id(TICKET_ID))
t = ticket.get_ticket_content(TICKET_ID)
self.assertEqual(t, SUCCESS_RESULT._replace(url=None, ticket_content={'header': ['200 OK']}))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_create_no_subject(self, mock_session):
Expand Down Expand Up @@ -223,7 +232,8 @@ def test_create_ticket_request(self, mock_session, mock_url):
mock_session.return_value = FakeSession(status_code=201)
ticket = rt.RTTicket(URL, PROJECT)
request_result = ticket._create_ticket_request('')
self.assertEqual(request_result, SUCCESS_RESULT._replace(url=None))
self.assertEqual(request_result,
SUCCESS_RESULT._replace(url=None, ticket_content={'header': ['201 Ticket 007 created']}))
self.assertEqual(ticket.ticket_id, '007')
self.assertEqual(ticket.ticket_url, mock_url.return_value)

Expand Down Expand Up @@ -254,7 +264,7 @@ def test_edit(self, mock_session):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT, ticket_id=TICKET_ID)
request_result = ticket.edit()
self.assertEqual(request_result, SUCCESS_RESULT)
self.assertEqual(request_result, SUCCESS_RESULT._replace(ticket_content={'header': ['200 OK']}))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_add_comment_no_ticket_id(self, mock_session):
Expand Down Expand Up @@ -284,7 +294,7 @@ def test_add_comment(self, mock_session):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT, ticket_id=TICKET_ID)
request_result = ticket.add_comment('')
self.assertEqual(request_result, SUCCESS_RESULT)
self.assertEqual(request_result, SUCCESS_RESULT._replace(ticket_content={'header': ['200 OK']}))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_change_status_no_ticket_id(self, mock_session):
Expand Down Expand Up @@ -313,7 +323,7 @@ def test_change_status(self, mock_session):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT, ticket_id=TICKET_ID)
request_result = ticket.change_status('')
self.assertEqual(request_result, SUCCESS_RESULT)
self.assertEqual(request_result, SUCCESS_RESULT._replace(ticket_content={'header': ['200 OK']}))

@patch.object(rt.RTTicket, '_create_requests_session')
def test_add_attachment_no_ticket_id(self, mock_session):
Expand Down Expand Up @@ -353,14 +363,22 @@ def test_add_attachment(self, mock_session, mock_open):
mock_session.return_value = FakeSession()
ticket = rt.RTTicket(URL, PROJECT, ticket_id=TICKET_ID)
request_result = ticket.add_attachment('file_name')
self.assertEqual(request_result, SUCCESS_RESULT)
self.assertEqual(request_result, SUCCESS_RESULT._replace(ticket_content={'header': ['200 OK']}))

def test_prepare_ticket_fields(self):
fields = {'cc': 'something', 'admincc': ['me', 'you']}
expected_result = {'cc': 'something', 'admincc': 'me, you'}
result = rt._prepare_ticket_fields(fields)
self.assertEqual(result, expected_result)

def test_convert_string(self):
string1 = 'Header1\n\nHeader2\n id: 1\n Attachments: 1000: file'
string2 = 'Header1\n\n Stack:\n id: 1\n Attachments: 1000: file'
expected_result1 = {'header': ['Header1', 'Header2'], 'id': '1', '1000': 'file'}
expected_result2 = ['Header1', '', ' Stack:', ' id: 1', ' Attachments: 1000: file']
self.assertDictEqual(rt._convert_string(string1), expected_result1)
self.assertEqual(rt._convert_string(string2), expected_result2)


if __name__ == '__main__':
main()
66 changes: 53 additions & 13 deletions ticketutil/rt.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,30 +101,42 @@ def _verify_project(self, project):
logger.debug("Project {0} is valid".format(project))
return True

def _verify_ticket_id(self, ticket_id):
def get_ticket_content(self, ticket_id=None, option='show'):
"""
Queries the RT API to see if ticket_id is a valid ticket for the given RT instance.
:param ticket_id: The ticket you're verifying.
:return: True or False depending on if ticket is valid.
Queries the RT API to get the ticket_content using ticket_id.
:param ticket_id: ticket number, if not set self.ticket_id is used.
:param option: specifies the type of content with possible values 'show', 'attachments', 'comment' and
'history'
:return: self.request_result: Named tuple containing request status, error_message, url info and
ticket_content.
"""
if ticket_id is None:
ticket_id = self.ticket_id
if not self.ticket_id:
error_message = "No ticket ID associated with ticket object. " \
"Set ticket ID with set_ticket_id(<ticket_id>)"
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)
try:
r = self.s.get("{0}/ticket/{1}/show".format(self.rest_url, ticket_id))
logger.debug("Verify ticket_id: status code: {0}".format(r.status_code))
r = self.s.get("{0}/ticket/{1}/{2}".format(self.rest_url, ticket_id, option))
logger.debug("Get ticket content: status code: {0}".format(r.status_code))
r.raise_for_status()
except requests.RequestException as e:
logger.error("Unexpected error occurred when verifying ticket_id")
error_message = "Error getting ticket content"
logger.error(error_message)
logger.error(e)
return False
return self.request_result._replace(status='Failure', error_message=error_message)

# RT's API returns 200 even if the ticket is not valid. We need to parse the response.
error_responses = ["Ticket {0} does not exist.".format(ticket_id),
"Bad Request"]
if any(error in r.text for error in error_responses):
logger.error("Ticket {0} is not valid".format(ticket_id))
return False
else:
logger.debug("Ticket {0} is valid".format(ticket_id))
return True
error_message = "Ticket {0} is not valid".format(ticket_id)
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)

self.ticket_content = _convert_string(r.text)
return self.request_result._replace(ticket_content=self.ticket_content)

def create(self, subject, text, **kwargs):
"""
Expand Down Expand Up @@ -216,6 +228,7 @@ def _create_ticket_request(self, params):
self.ticket_id = re.search('Ticket (\d+) created', ticket_content).groups()[0]
self.ticket_url = self._generate_ticket_url()
logger.info("Created ticket {0} - {1}".format(self.ticket_id, self.ticket_url))
self.request_result = self.get_ticket_content()
return self.request_result

def edit(self, **kwargs):
Expand Down Expand Up @@ -263,6 +276,7 @@ def edit(self, **kwargs):
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)
logger.info("Edited ticket {0} - {1}".format(self.ticket_id, self.ticket_url))
self.request_result = self.get_ticket_content()
return self.request_result

def add_comment(self, comment):
Expand Down Expand Up @@ -300,6 +314,7 @@ def add_comment(self, comment):
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)
logger.info("Added comment to ticket {0} - {1}".format(self.ticket_id, self.ticket_url))
self.request_result = self.get_ticket_content()
return self.request_result

def change_status(self, status):
Expand Down Expand Up @@ -333,6 +348,7 @@ def change_status(self, status):
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)
logger.info("Changed status of ticket {0} - {1}".format(self.ticket_id, self.ticket_url))
self.request_result = self.get_ticket_content()
return self.request_result

def add_attachment(self, file_name):
Expand Down Expand Up @@ -373,6 +389,7 @@ def add_attachment(self, file_name):
logger.error(error_message)
return self.request_result._replace(status='Failure', error_message=error_message)
logger.info("Attached file {0} to ticket {1} - {2}".format(file_name, self.ticket_id, self.ticket_url))
self.request_result = self.get_ticket_content()
return self.request_result


Expand All @@ -389,6 +406,29 @@ def _prepare_ticket_fields(fields):
return fields


def _convert_string(text):
"""
Converts string into more appropriate form for reading and accessing.
:param text: Text to be converted in a form of string.
:return: List of strings or a dictionary in dependance of which form is preferable.
"""
lines = text.split('\n')
if 'Stack' in text:
return text.split('\n')
response = {'header': []}
for line in lines:
if line:
if ':' not in line:
response['header'].append(line)
elif ':' in line:
elements = line.split(':')
if 'Attachments' in elements[0]:
response[elements[1].lstrip()] = elements[2].lstrip()
else:
response[elements[0].lstrip()] = elements[1].lstrip()
return response


def main():
"""
main() function, not directly callable.
Expand Down

0 comments on commit df114f1

Please sign in to comment.