BOM-70 (#21327)
* Update Financial Assistance logic Use the zendesk proxy app instead of the unsupported zendesk library. * Move to pre-fetching the group IDs. Rather than making extra requests to zendesk to list all groups and find a specific group ID. Just make a pre-filled list of group IDs for the groups we care about. When a group name is passed in, it is checked against this list and the ticket is created in the correct group so the right people can respond to it.
This commit is contained in:
@@ -7,11 +7,9 @@ from functools import wraps
|
||||
|
||||
import calc
|
||||
import crum
|
||||
import zendesk
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.cache import caches
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseServerError
|
||||
from django.views.decorators.csrf import requires_csrf_token
|
||||
from django.views.defaults import server_error
|
||||
from opaque_keys import InvalidKeyError
|
||||
@@ -20,8 +18,6 @@ from six.moves import map
|
||||
|
||||
import track.views
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import GlobalStaff
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -168,232 +164,6 @@ def calculate(request):
|
||||
return HttpResponse(json.dumps({'result': str(result)}))
|
||||
|
||||
|
||||
class _ZendeskApi(object):
|
||||
|
||||
CACHE_PREFIX = 'ZENDESK_API_CACHE'
|
||||
CACHE_TIMEOUT = 60 * 60
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Instantiate the Zendesk API.
|
||||
|
||||
All of `ZENDESK_URL`, `ZENDESK_USER`, and `ZENDESK_API_KEY` must be set
|
||||
in `django.conf.settings`.
|
||||
"""
|
||||
self._zendesk_instance = zendesk.Zendesk(
|
||||
settings.ZENDESK_URL,
|
||||
settings.ZENDESK_USER,
|
||||
settings.ZENDESK_API_KEY,
|
||||
use_api_token=True,
|
||||
api_version=2,
|
||||
# As of 2012-05-08, Zendesk is using a CA that is not
|
||||
# installed on our servers
|
||||
client_args={"disable_ssl_certificate_validation": True}
|
||||
)
|
||||
|
||||
def create_ticket(self, ticket):
|
||||
"""
|
||||
Create the given `ticket` in Zendesk.
|
||||
|
||||
The ticket should have the format specified by the zendesk package.
|
||||
"""
|
||||
ticket_url = self._zendesk_instance.create_ticket(data=ticket)
|
||||
return zendesk.get_id_from_url(ticket_url)
|
||||
|
||||
def update_ticket(self, ticket_id, update):
|
||||
"""
|
||||
Update the Zendesk ticket with id `ticket_id` using the given `update`.
|
||||
|
||||
The update should have the format specified by the zendesk package.
|
||||
"""
|
||||
self._zendesk_instance.update_ticket(ticket_id=ticket_id, data=update)
|
||||
|
||||
def get_group(self, name):
|
||||
"""
|
||||
Find the Zendesk group named `name`. Groups are cached for
|
||||
CACHE_TIMEOUT seconds.
|
||||
|
||||
If a matching group exists, it is returned as a dictionary
|
||||
with the format specifed by the zendesk package.
|
||||
|
||||
Otherwise, returns None.
|
||||
"""
|
||||
cache = caches['default']
|
||||
cache_key = '{prefix}_group_{name}'.format(prefix=self.CACHE_PREFIX, name=name)
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
groups = self._zendesk_instance.list_groups()['groups']
|
||||
for group in groups:
|
||||
if group['name'] == name:
|
||||
cache.set(cache_key, group, self.CACHE_TIMEOUT)
|
||||
return group
|
||||
return None
|
||||
|
||||
|
||||
def _get_zendesk_custom_field_context(request, **kwargs):
|
||||
"""
|
||||
Construct a dictionary of data that can be stored in Zendesk custom fields.
|
||||
"""
|
||||
context = {}
|
||||
|
||||
course_id = request.POST.get("course_id")
|
||||
if not course_id:
|
||||
return context
|
||||
|
||||
context["course_id"] = course_id
|
||||
if not request.user.is_authenticated:
|
||||
return context
|
||||
|
||||
enrollment = CourseEnrollment.get_enrollment(request.user, CourseKey.from_string(course_id))
|
||||
if enrollment and enrollment.is_active:
|
||||
context["enrollment_mode"] = enrollment.mode
|
||||
|
||||
enterprise_learner_data = kwargs.get('learner_data', None)
|
||||
if enterprise_learner_data:
|
||||
enterprise_customer_name = enterprise_learner_data[0]['enterprise_customer']['name']
|
||||
context["enterprise_customer_name"] = enterprise_customer_name
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def _format_zendesk_custom_fields(context):
|
||||
"""
|
||||
Format the data in `context` for compatibility with the Zendesk API.
|
||||
Ignore any keys that have not been configured in `ZENDESK_CUSTOM_FIELDS`.
|
||||
"""
|
||||
custom_fields = []
|
||||
for key, val, in settings.ZENDESK_CUSTOM_FIELDS.items():
|
||||
if key in context:
|
||||
custom_fields.append({"id": val, "value": context[key]})
|
||||
|
||||
return custom_fields
|
||||
|
||||
|
||||
def _record_feedback_in_zendesk(
|
||||
realname,
|
||||
email,
|
||||
subject,
|
||||
details,
|
||||
tags,
|
||||
additional_info,
|
||||
group_name=None,
|
||||
require_update=False,
|
||||
support_email=None,
|
||||
custom_fields=None
|
||||
):
|
||||
"""
|
||||
Create a new user-requested Zendesk ticket.
|
||||
|
||||
Once created, the ticket will be updated with a private comment containing
|
||||
additional information from the browser and server, such as HTTP headers
|
||||
and user state. Returns a boolean value indicating whether ticket creation
|
||||
was successful, regardless of whether the private comment update succeeded.
|
||||
|
||||
If `group_name` is provided, attaches the ticket to the matching Zendesk group.
|
||||
|
||||
If `require_update` is provided, returns False when the update does not
|
||||
succeed. This allows using the private comment to add necessary information
|
||||
which the user will not see in followup emails from support.
|
||||
|
||||
If `custom_fields` is provided, submits data to those fields in Zendesk.
|
||||
"""
|
||||
zendesk_api = _ZendeskApi()
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
# Tag all issues with LMS to distinguish channel in Zendesk; requested by student support team
|
||||
zendesk_tags = list(tags.values()) + ["LMS"]
|
||||
|
||||
# Per edX support, we would like to be able to route feedback items by site via tagging
|
||||
current_site_name = configuration_helpers.get_value("SITE_NAME")
|
||||
if current_site_name:
|
||||
current_site_name = current_site_name.replace(".", "_")
|
||||
zendesk_tags.append("site_name_{site}".format(site=current_site_name))
|
||||
|
||||
new_ticket = {
|
||||
"ticket": {
|
||||
"requester": {"name": realname, "email": email},
|
||||
"subject": subject,
|
||||
"comment": {"body": details},
|
||||
"tags": zendesk_tags
|
||||
}
|
||||
}
|
||||
|
||||
if custom_fields:
|
||||
new_ticket["ticket"]["custom_fields"] = custom_fields
|
||||
|
||||
group = None
|
||||
if group_name is not None:
|
||||
group = zendesk_api.get_group(group_name)
|
||||
if group is not None:
|
||||
new_ticket['ticket']['group_id'] = group['id']
|
||||
if support_email is not None:
|
||||
# If we do not include the `recipient` key here, Zendesk will default to using its default reply
|
||||
# email address when support agents respond to tickets. By setting the `recipient` key here,
|
||||
# we can ensure that WL site users are responded to via the correct Zendesk support email address.
|
||||
new_ticket['ticket']['recipient'] = support_email
|
||||
try:
|
||||
ticket_id = zendesk_api.create_ticket(new_ticket)
|
||||
if group_name is not None and group is None:
|
||||
# Support uses Zendesk groups to track tickets. In case we
|
||||
# haven't been able to correctly group this ticket, log its ID
|
||||
# so it can be found later.
|
||||
log.warning('Unable to find group named %s for Zendesk ticket with ID %s.', group_name, ticket_id)
|
||||
except zendesk.ZendeskError:
|
||||
log.exception("Error creating Zendesk ticket")
|
||||
return False
|
||||
|
||||
# Additional information is provided as a private update so the information
|
||||
# is not visible to the user.
|
||||
ticket_update = {"ticket": {"comment": {"public": False, "body": additional_info_string}}}
|
||||
try:
|
||||
zendesk_api.update_ticket(ticket_id, ticket_update)
|
||||
except zendesk.ZendeskError:
|
||||
log.exception("Error updating Zendesk ticket with ID %s.", ticket_id)
|
||||
# The update is not strictly necessary, so do not indicate
|
||||
# failure to the user unless it has been requested with
|
||||
# `require_update`.
|
||||
if require_update:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_feedback_form_context(request):
|
||||
"""
|
||||
Extract the submitted form fields to be used as a context for
|
||||
feedback submission.
|
||||
"""
|
||||
context = {}
|
||||
|
||||
context["subject"] = request.POST["subject"]
|
||||
context["details"] = request.POST["details"]
|
||||
context["tags"] = dict(
|
||||
[(tag, request.POST[tag]) for tag in ["issue_type", "course_id"] if request.POST.get(tag)]
|
||||
)
|
||||
|
||||
context["additional_info"] = {}
|
||||
|
||||
if request.user.is_authenticated:
|
||||
context["realname"] = request.user.profile.name
|
||||
context["email"] = request.user.email
|
||||
context["additional_info"]["username"] = request.user.username
|
||||
else:
|
||||
context["realname"] = request.POST["name"]
|
||||
context["email"] = request.POST["email"]
|
||||
|
||||
for header, pretty in [("HTTP_REFERER", "Page"), ("HTTP_USER_AGENT", "Browser"), ("REMOTE_ADDR", "Client IP"),
|
||||
("SERVER_NAME", "Host")]:
|
||||
context["additional_info"][pretty] = request.META.get(header)
|
||||
|
||||
context["support_email"] = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def info(request):
|
||||
''' Info page (link from main header) '''
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
Reference in New Issue
Block a user