Commit 7c326f65 by Greg Price

Merge pull request #120 from edx/gprice/help-widget-tweaks

Reviewed by @brianhw https://rbcommons.com/s/edx/r/17/
parents 673c015e 2e38130c
...@@ -12,6 +12,7 @@ from django.core.validators import ValidationError, validate_email ...@@ -12,6 +12,7 @@ from django.core.validators import ValidationError, validate_email
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpResponseServerError from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, HttpResponseServerError
from django.shortcuts import redirect from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from dogapi import dog_stats_api
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from urllib import urlencode from urllib import urlencode
import zendesk import zendesk
...@@ -73,11 +74,64 @@ class _ZendeskApi(object): ...@@ -73,11 +74,64 @@ class _ZendeskApi(object):
self._zendesk_instance.update_ticket(ticket_id=ticket_id, data=update) self._zendesk_instance.update_ticket(ticket_id=ticket_id, data=update)
def submit_feedback_via_zendesk(request): def _record_feedback_in_zendesk(realname, email, subject, details, tags, additional_info):
""" """
Create a new user-requested Zendesk ticket. Create a new user-requested Zendesk ticket.
If Zendesk submission is not enabled, any request will raise `Http404`. 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.
"""
zendesk_api = _ZendeskApi()
additional_info_string = (
"Additional information:\n\n" +
"\n".join("%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"]
new_ticket = {
"ticket": {
"requester": {"name": realname, "email": email},
"subject": subject,
"comment": {"body": details},
"tags": zendesk_tags
}
}
try:
ticket_id = zendesk_api.create_ticket(new_ticket)
except zendesk.ZendeskError as err:
log.error("Error creating Zendesk ticket: %s", str(err))
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 as err:
log.error("Error updating Zendesk ticket: %s", str(err))
# The update is not strictly necessary, so do not indicate failure to the user
pass
return True
DATADOG_FEEDBACK_METRIC = "lms_feedback_submissions"
def _record_feedback_in_datadog(tags):
datadog_tags = ["{k}:{v}".format(k=k, v=v) for k, v in tags.items()]
dog_stats_api.increment(DATADOG_FEEDBACK_METRIC, tags=datadog_tags)
def submit_feedback(request):
"""
Create a new user-requested ticket, currently implemented with Zendesk.
If feedback submission is not enabled, any request will raise `Http404`.
If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or
`ZENDESK_API_KEY`) is missing, any request will raise an `Exception`. `ZENDESK_API_KEY`) is missing, any request will raise an `Exception`.
The request must be a POST request specifying `subject` and `details`. The request must be a POST request specifying `subject` and `details`.
...@@ -85,12 +139,9 @@ def submit_feedback_via_zendesk(request): ...@@ -85,12 +139,9 @@ def submit_feedback_via_zendesk(request):
`email`. If the user is authenticated, the `name` and `email` will be `email`. If the user is authenticated, the `name` and `email` will be
populated from the user's information. If any required parameter is populated from the user's information. If any required parameter is
missing, a 400 error will be returned indicating which field is missing and missing, a 400 error will be returned indicating which field is missing and
providing an error message. If Zendesk returns any error on ticket providing an error message. If Zendesk ticket creation fails, 500 error
creation, a 500 error will be returned with no body. Once created, the will be returned with no body; if ticket creation succeeds, an empty
ticket will be updated with a private comment containing additional successful response (200) will be returned.
information from the browser and server, such as HTTP headers and user
state. Whether or not the update succeeds, if the user's ticket is
successfully created, an empty successful response (200) will be returned.
""" """
if not settings.MITX_FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False): if not settings.MITX_FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
raise Http404() raise Http404()
...@@ -124,9 +175,9 @@ def submit_feedback_via_zendesk(request): ...@@ -124,9 +175,9 @@ def submit_feedback_via_zendesk(request):
subject = request.POST["subject"] subject = request.POST["subject"]
details = request.POST["details"] details = request.POST["details"]
tags = [] tags = dict(
if "tag" in request.POST: [(tag, request.POST[tag]) for tag in ["issue_type", "course_id"] if tag in request.POST]
tags = [request.POST["tag"]] )
if request.user.is_authenticated(): if request.user.is_authenticated():
realname = request.user.profile.name realname = request.user.profile.name
...@@ -140,41 +191,18 @@ def submit_feedback_via_zendesk(request): ...@@ -140,41 +191,18 @@ def submit_feedback_via_zendesk(request):
except ValidationError: except ValidationError:
return build_error_response(400, "email", required_field_errs["email"]) return build_error_response(400, "email", required_field_errs["email"])
for header in ["HTTP_REFERER", "HTTP_USER_AGENT"]: for header, pretty in [
additional_info[header] = request.META.get(header) ("HTTP_REFERER", "Page"),
("HTTP_USER_AGENT", "Browser"),
("REMOTE_ADDR", "Client IP"),
("SERVER_NAME", "Host")
]:
additional_info[pretty] = request.META.get(header)
zendesk_api = _ZendeskApi() success = _record_feedback_in_zendesk(realname, email, subject, details, tags, additional_info)
_record_feedback_in_datadog(tags)
additional_info_string = (
"Additional information:\n\n" +
"\n".join("%s: %s" % (key, value) for (key, value) in additional_info.items() if value is not None)
)
new_ticket = {
"ticket": {
"requester": {"name": realname, "email": email},
"subject": subject,
"comment": {"body": details},
"tags": tags
}
}
try:
ticket_id = zendesk_api.create_ticket(new_ticket)
except zendesk.ZendeskError as err:
log.error("Error creating Zendesk ticket: %s", str(err))
return HttpResponse(status=500)
# 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 as err:
log.error("Error updating Zendesk ticket: %s", str(err))
# The update is not strictly necessary, so do not indicate failure to the user
pass
return HttpResponse() return HttpResponse(status=(200 if success else 500))
def info(request): def info(request):
......
...@@ -64,7 +64,10 @@ discussion_link = get_discussion_link(course) if course else None ...@@ -64,7 +64,10 @@ discussion_link = get_discussion_link(course) if course else None
<label data-field="details">Tell us the details* <label data-field="details">Tell us the details*
<span class="tip">Include error messages, steps which lead to the issue, etc</span></label> <span class="tip">Include error messages, steps which lead to the issue, etc</span></label>
<textarea name="details"></textarea> <textarea name="details"></textarea>
<input name="tag" type="hidden"> <input name="issue_type" type="hidden">
% if course:
<input name="course_id" type="hidden" value="${course.id | h}">
% endif
<div class="submit"> <div class="submit">
<input name="submit" type="submit" value="Submit"> <input name="submit" type="submit" value="Submit">
</div> </div>
...@@ -114,21 +117,41 @@ discussion_link = get_discussion_link(course) if course else None ...@@ -114,21 +117,41 @@ discussion_link = get_discussion_link(course) if course else None
$("#feedback_success_wrapper").css("display", "none"); $("#feedback_success_wrapper").css("display", "none");
$("#help_wrapper").css("display", "block"); $("#help_wrapper").css("display", "block");
}); });
showFeedback = function(e, tag, title) { showFeedback = function(event, issue_type, title, subject_label, details_label) {
$("#help_wrapper").css("display", "none"); $("#help_wrapper").css("display", "none");
$("#feedback_form input[name='tag']").val(tag); $("#feedback_form input[name='issue_type']").val(issue_type);
$("#feedback_form_wrapper").css("display", "block"); $("#feedback_form_wrapper").css("display", "block");
$("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>"); $("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>");
e.preventDefault(); $("#feedback_form_wrapper label[data-field='subject']").html(subject_label);
$("#feedback_form_wrapper label[data-field='details']").html(details_label);
event.preventDefault();
}; };
$("#feedback_link_problem").click(function(e) { $("#feedback_link_problem").click(function(event) {
showFeedback(e, "problem", "Report a Problem"); showFeedback(
event,
"problem",
"Report a Problem",
"Brief description of the problem*",
"Details of the problem you are encountering* <span class='tip'>Include error messages, steps which lead to the issue, etc.</span>"
);
}); });
$("#feedback_link_suggestion").click(function(e) { $("#feedback_link_suggestion").click(function(event) {
showFeedback(e, "suggestion", "Make a Suggestion"); showFeedback(
event,
"suggestion",
"Make a Suggestion",
"Brief description of your suggestion*",
"Details*"
);
}); });
$("#feedback_link_question").click(function(e) { $("#feedback_link_question").click(function(event) {
showFeedback(e, "question", "Ask a Question"); showFeedback(
event,
"question",
"Ask a Question",
"Brief summary of your question*",
"Details*"
);
}); });
$("#feedback_form").submit(function() { $("#feedback_form").submit(function() {
$("input[type='submit']", this).attr("disabled", "disabled"); $("input[type='submit']", this).attr("disabled", "disabled");
......
...@@ -111,7 +111,7 @@ if not settings.MITX_FEATURES["USE_CUSTOM_THEME"]: ...@@ -111,7 +111,7 @@ if not settings.MITX_FEATURES["USE_CUSTOM_THEME"]:
# Favicon # Favicon
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}), (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),
url(r'^submit_feedback$', 'util.views.submit_feedback_via_zendesk'), url(r'^submit_feedback$', 'util.views.submit_feedback'),
# TODO: These urls no longer work. They need to be updated before they are re-enabled # TODO: These urls no longer work. They need to be updated before they are re-enabled
# url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'), # url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment