Merge pull request #21229 from edx/BOM-70

BOM-70
This commit is contained in:
Ayub
2019-08-06 18:48:42 +05:00
committed by GitHub
15 changed files with 177 additions and 1389 deletions

View File

@@ -3,11 +3,15 @@ Tests of Zendesk interaction utility functions
"""
from __future__ import absolute_import
import ddt
from django.test.utils import override_settings
from mock import MagicMock, patch
from openedx.core.djangoapps.zendesk_proxy.utils import create_zendesk_ticket
import json
from collections import OrderedDict
from django.test.utils import override_settings
import ddt
from mock import MagicMock, patch
from openedx.core.djangoapps.zendesk_proxy.utils import create_zendesk_ticket, get_zendesk_group_by_name
from openedx.core.lib.api.test_utils import ApiTestCase
@@ -61,3 +65,51 @@ class TestUtils(ApiTestCase):
body=self.request_data['body'],
)
self.assertEqual(status_code, 500)
def test_financial_assistant_ticket(self):
""" Test Financial Assistent request ticket. """
ticket_creation_response_data = {
"ticket": {
"id": 35436,
"subject": "My printer is on fire!",
}
}
response_text = json.dumps(ticket_creation_response_data)
with patch('requests.post', return_value=MagicMock(status_code=200, text=response_text)):
with patch('requests.put', return_value=MagicMock(status_code=200)):
with patch('openedx.core.djangoapps.zendesk_proxy.utils.get_zendesk_group_by_name', return_value=2):
status_code = create_zendesk_ticket(
requester_name=self.request_data['name'],
requester_email=self.request_data['email'],
subject=self.request_data['subject'],
body=self.request_data['body'],
group='Financial Assistant',
additional_info=OrderedDict(
(
('Username', 'test'),
('Full Name', 'Legal Name'),
('Course ID', 'course_key'),
('Annual Household Income', 'Income'),
('Country', 'Country'),
)
),
)
self.assertEqual(status_code, 200)
def test_get_zendesk_group_by_name(self):
""" Tests the functionality of the get zendesk group. """
response_data = {
"groups": [
{
"name": "DJs",
"created_at": "2009-05-13T00:07:08Z",
"updated_at": "2011-07-22T00:11:12Z",
"id": 211
}
]
}
response_text = json.dumps(response_data)
with patch('requests.get', return_value=MagicMock(status_code=200, text=response_text)):
group_id = get_zendesk_group_by_name('DJs')
self.assertEqual(group_id, 211)

View File

@@ -3,29 +3,44 @@ Utility functions for zendesk interaction.
"""
from __future__ import absolute_import
import json
import logging
from six.moves.urllib.parse import urljoin # pylint: disable=import-error
from django.conf import settings
import requests
from django.conf import settings
from rest_framework import status
from six.moves.urllib.parse import urljoin # pylint: disable=import-error
log = logging.getLogger(__name__)
def create_zendesk_ticket(requester_name, requester_email, subject, body, custom_fields=None, uploads=None, tags=None):
def _std_error_message(details, payload):
"""Internal helper to standardize error message. This allows for simpler splunk alerts."""
return u'zendesk_proxy action required\n{}\nNo ticket created for payload {}'.format(details, payload)
def _get_request_headers():
return {
'content-type': 'application/json',
'Authorization': u"Bearer {}".format(settings.ZENDESK_OAUTH_ACCESS_TOKEN),
}
def create_zendesk_ticket(
requester_name,
requester_email,
subject,
body,
group=None,
custom_fields=None,
uploads=None,
tags=None,
additional_info=None
):
"""
Create a Zendesk ticket via API.
Note that we do this differently in other locations (lms/djangoapps/commerce/signals.py and
common/djangoapps/util/views.py). Both of those callers use basic auth, and should be switched over to this oauth
implementation once the immediate pressures of zendesk_proxy are resolved.
"""
def _std_error_message(details, payload):
"""Internal helper to standardize error message. This allows for simpler splunk alerts."""
return u'zendesk_proxy action required\n{}\nNo ticket created for payload {}'.format(details, payload)
if tags:
# Remove duplicates from tags list
tags = list(set(tags))
@@ -46,22 +61,22 @@ def create_zendesk_ticket(requester_name, requester_email, subject, body, custom
}
}
if not (settings.ZENDESK_URL and settings.ZENDESK_OAUTH_ACCESS_TOKEN):
log.error(_std_error_message("zendesk not configured", data))
return status.HTTP_503_SERVICE_UNAVAILABLE
if group:
group_id = get_zendesk_group_by_name(group)
data['ticket']['group_id'] = group_id
# Encode the data to create a JSON payload
payload = json.dumps(data)
if not (settings.ZENDESK_URL and settings.ZENDESK_OAUTH_ACCESS_TOKEN):
log.error(_std_error_message("zendesk not configured", payload))
return status.HTTP_503_SERVICE_UNAVAILABLE
# Set the request parameters
url = urljoin(settings.ZENDESK_URL, '/api/v2/tickets.json')
headers = {
'content-type': 'application/json',
'Authorization': u"Bearer {}".format(settings.ZENDESK_OAUTH_ACCESS_TOKEN),
}
try:
response = requests.post(url, data=payload, headers=headers)
response = requests.post(url, data=payload, headers=_get_request_headers())
# Check for HTTP codes other than 201 (Created)
if response.status_code == status.HTTP_201_CREATED:
@@ -73,7 +88,72 @@ def create_zendesk_ticket(requester_name, requester_email, subject, body, custom
payload
)
)
if additional_info:
ticket = json.loads(response.text)['ticket']
return post_additional_info_as_comment(ticket['id'], additional_info)
return response.status_code
except Exception: # pylint: disable=broad-except
log.exception(_std_error_message('Internal server error', payload))
return status.HTTP_500_INTERNAL_SERVER_ERROR
def get_zendesk_group_by_name(name):
"""
Calls the Zendesk list-groups api
Returns the group Id matching the name.
"""
url = urljoin(settings.ZENDESK_URL, '/api/v2/groups.json')
try:
response = requests.get(url, headers=_get_request_headers())
groups = json.loads(response.text)['groups']
for group in groups:
if group['name'] == name:
return group['id']
except Exception: # pylint: disable=broad-except
log.exception(_std_error_message('Internal server error', 'None'))
return status.HTTP_500_INTERNAL_SERVER_ERROR
log.exception(_std_error_message('Tried to get zendesk group which does not exist', name))
raise Exception
def post_additional_info_as_comment(ticket_id, additional_info):
"""
Post the Additional Provided as a comment, So that it is only visible
to management and not students.
"""
additional_info_string = (
u"Additional information:\n\n" +
u"\n".join(u"%s: %s" % (key, value) for (key, value) in additional_info.items() if value is not None)
)
data = {
'ticket': {
'comment': {
'body': additional_info_string,
'publuc': False
}
}
}
url = urljoin(settings.ZENDESK_URL, 'api/v2/tickets/{}.json'.format(ticket_id))
try:
response = requests.put(url, data=json.dumps(data), headers=_get_request_headers())
if response.status_code >= 200 and response.status_code < 300:
log.debug(u'Successfully created comment for ticket {}'.format(ticket_id))
else:
log.error(
_std_error_message(
u'Unexpected response: {} - {}'.format(response.status_code, response.content),
data
)
)
return response.status_code
except Exception: # pylint: disable=broad-except
log.exception(_std_error_message('Internal server error', data))
return status.HTTP_500_INTERNAL_SERVER_ERROR