Merge pull request #1911 from edx/feature/gprice/feedback-button
This commit is contained in:
@@ -294,6 +294,27 @@ def get_course_tabs(user, course, active_page):
|
||||
return tabs
|
||||
|
||||
|
||||
def get_discussion_link(course):
|
||||
"""
|
||||
Return the URL for the discussion tab for the given `course`.
|
||||
|
||||
If they have a discussion link specified, use that even if we disable
|
||||
discussions. Disabling discsussions is mostly a server safety feature at
|
||||
this point, and we don't need to worry about external sites. Otherwise,
|
||||
if the course has a discussion tab or uses the default tabs, return the
|
||||
discussion view URL. Otherwise, return None to indicate the lack of a
|
||||
discussion tab.
|
||||
"""
|
||||
if course.discussion_link:
|
||||
return course.discussion_link
|
||||
elif not settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
|
||||
return None
|
||||
elif hasattr(course, 'tabs') and course.tabs and not any([tab['type'] == 'discussion' for tab in course.tabs]):
|
||||
return None
|
||||
else:
|
||||
return reverse('django_comment_client.forum.views.forum_form_discussion', args=[course.id])
|
||||
|
||||
|
||||
def get_default_tabs(user, course, active_page):
|
||||
|
||||
# When calling the various _tab methods, can omit the 'type':'blah' from the
|
||||
@@ -308,15 +329,9 @@ def get_default_tabs(user, course, active_page):
|
||||
|
||||
tabs.extend(_textbooks({}, user, course, active_page))
|
||||
|
||||
## If they have a discussion link specified, use that even if we feature
|
||||
## flag discussions off. Disabling that is mostly a server safety feature
|
||||
## at this point, and we don't need to worry about external sites.
|
||||
if course.discussion_link:
|
||||
tabs.append(CourseTab('Discussion', course.discussion_link, active_page == 'discussion'))
|
||||
elif settings.MITX_FEATURES.get('ENABLE_DISCUSSION_SERVICE'):
|
||||
link = reverse('django_comment_client.forum.views.forum_form_discussion',
|
||||
args=[course.id])
|
||||
tabs.append(CourseTab('Discussion', link, active_page == 'discussion'))
|
||||
discussion_link = get_discussion_link(course)
|
||||
if discussion_link:
|
||||
tabs.append(CourseTab('Discussion', discussion_link, active_page == 'discussion'))
|
||||
|
||||
tabs.extend(_wiki({'name': 'Wiki', 'type': 'wiki'}, user, course, active_page))
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
from django.test import TestCase
|
||||
from mock import MagicMock
|
||||
from mock import patch
|
||||
|
||||
import courseware.tabs as tabs
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
class ProgressTestCase(TestCase):
|
||||
|
||||
@@ -257,3 +261,62 @@ class ValidateTabsTestCase(TestCase):
|
||||
self.assertRaises(tabs.InvalidTabsException, tabs.validate_tabs, self.courses[2])
|
||||
self.assertIsNone(tabs.validate_tabs(self.courses[3]))
|
||||
self.assertRaises(tabs.InvalidTabsException, tabs.validate_tabs, self.courses[4])
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class DiscussionLinkTestCase(ModuleStoreTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.tabs_with_discussion = [
|
||||
{'type':'courseware'},
|
||||
{'type':'course_info'},
|
||||
{'type':'discussion'},
|
||||
{'type':'textbooks'},
|
||||
]
|
||||
self.tabs_without_discussion = [
|
||||
{'type':'courseware'},
|
||||
{'type':'course_info'},
|
||||
{'type':'textbooks'},
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _patch_reverse(course):
|
||||
def patched_reverse(viewname, args):
|
||||
if viewname == "django_comment_client.forum.views.forum_form_discussion" and args == [course.id]:
|
||||
return "default_discussion_link"
|
||||
else:
|
||||
return None
|
||||
return patch("courseware.tabs.reverse", patched_reverse)
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"ENABLE_DISCUSSION_SERVICE": False})
|
||||
def test_explicit_discussion_link(self):
|
||||
"""Test that setting discussion_link overrides everything else"""
|
||||
course = CourseFactory.create(discussion_link="other_discussion_link", tabs=self.tabs_with_discussion)
|
||||
self.assertEqual(tabs.get_discussion_link(course), "other_discussion_link")
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"ENABLE_DISCUSSION_SERVICE": False})
|
||||
def test_discussions_disabled(self):
|
||||
"""Test that other cases return None with discussions disabled"""
|
||||
for i, t in enumerate([None, self.tabs_with_discussion, self.tabs_without_discussion]):
|
||||
course = CourseFactory.create(tabs=t, number=str(i))
|
||||
self.assertEqual(tabs.get_discussion_link(course), None)
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def test_no_tabs(self):
|
||||
"""Test a course without tabs configured"""
|
||||
course = CourseFactory.create(tabs=None)
|
||||
with self._patch_reverse(course):
|
||||
self.assertEqual(tabs.get_discussion_link(course), "default_discussion_link")
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def test_tabs_with_discussion(self):
|
||||
"""Test a course with a discussion tab configured"""
|
||||
course = CourseFactory.create(tabs=self.tabs_with_discussion)
|
||||
with self._patch_reverse(course):
|
||||
self.assertEqual(tabs.get_discussion_link(course), "default_discussion_link")
|
||||
|
||||
@patch.dict("django.conf.settings.MITX_FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
|
||||
def test_tabs_without_discussion(self):
|
||||
"""Test a course with tabs configured but without a discussion tab"""
|
||||
course = CourseFactory.create(tabs=self.tabs_without_discussion)
|
||||
self.assertEqual(tabs.get_discussion_link(course), None)
|
||||
|
||||
@@ -49,7 +49,6 @@ class TestGradebook(ModuleStoreTestCase):
|
||||
]
|
||||
|
||||
for user in self.users:
|
||||
UserProfileFactory.create(user=user)
|
||||
CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
|
||||
|
||||
for i in xrange(USER_COUNT-1):
|
||||
@@ -151,4 +150,4 @@ class TestLetterCutoffPolicy(TestGradebook):
|
||||
# User 0 has 0 on Homeworks [1]
|
||||
# User 0 has 0 on the class [1]
|
||||
# One use at the top of the page [1]
|
||||
self.assertEquals(3, self.response.content.count('grade_None'))
|
||||
self.assertEquals(3, self.response.content.count('grade_None'))
|
||||
|
||||
@@ -88,6 +88,8 @@ META_UNIVERSITIES = ENV_TOKENS.get('META_UNIVERSITIES', {})
|
||||
COMMENTS_SERVICE_URL = ENV_TOKENS.get("COMMENTS_SERVICE_URL", '')
|
||||
COMMENTS_SERVICE_KEY = ENV_TOKENS.get("COMMENTS_SERVICE_KEY", '')
|
||||
CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull')
|
||||
ZENDESK_URL = ENV_TOKENS.get("ZENDESK_URL")
|
||||
FEEDBACK_SUBMISSION_EMAIL = ENV_TOKENS.get("FEEDBACK_SUBMISSION_EMAIL")
|
||||
|
||||
############################## SECURE AUTH ITEMS ###############
|
||||
# Secret things: passwords, access keys, etc.
|
||||
@@ -123,3 +125,6 @@ DATADOG_API = AUTH_TOKENS.get("DATADOG_API")
|
||||
# Analytics dashboard server
|
||||
ANALYTICS_SERVER_URL = ENV_TOKENS.get("ANALYTICS_SERVER_URL")
|
||||
ANALYTICS_API_KEY = AUTH_TOKENS.get("ANALYTICS_API_KEY", "")
|
||||
|
||||
ZENDESK_USER = AUTH_TOKENS.get("ZENDESK_USER")
|
||||
ZENDESK_API_KEY = AUTH_TOKENS.get("ZENDESK_API_KEY")
|
||||
|
||||
@@ -90,7 +90,10 @@ MITX_FEATURES = {
|
||||
|
||||
# Give a UI to show a student's submission history in a problem by the
|
||||
# Staff Debug tool.
|
||||
'ENABLE_STUDENT_HISTORY_VIEW': True
|
||||
'ENABLE_STUDENT_HISTORY_VIEW': True,
|
||||
|
||||
# Provide a UI to allow users to submit feedback from the LMS
|
||||
'ENABLE_FEEDBACK_SUBMISSION': False,
|
||||
}
|
||||
|
||||
# Used for A/B testing
|
||||
@@ -323,6 +326,14 @@ WIKI_LINK_DEFAULT_LEVEL = 2
|
||||
PEARSONVUE_SIGNINPAGE_URL = "https://www1.pearsonvue.com/testtaker/signin/SignInPage/EDX"
|
||||
# TESTCENTER_ACCOMMODATION_REQUEST_EMAIL = "exam-help@edx.org"
|
||||
|
||||
##### Feedback submission mechanism #####
|
||||
FEEDBACK_SUBMISSION_EMAIL = None
|
||||
|
||||
##### Zendesk #####
|
||||
ZENDESK_URL = None
|
||||
ZENDESK_USER = None
|
||||
ZENDESK_API_KEY = None
|
||||
|
||||
################################# open ended grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
@@ -582,3 +593,4 @@ INSTALLED_APPS = (
|
||||
# Discussion forums
|
||||
'django_comment_client',
|
||||
)
|
||||
|
||||
|
||||
@@ -202,5 +202,62 @@ mark {
|
||||
}
|
||||
}
|
||||
|
||||
.help-tab {
|
||||
@include transform(rotate(-90deg));
|
||||
@include transform-origin(0 0);
|
||||
top: 50%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
|
||||
a:link, a:visited {
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
border-top-style: none;
|
||||
@include border-radius(0px 0px 10px 10px);
|
||||
background: transparentize(#fff, 0.25);
|
||||
color: transparentize(#333, 0.25);
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
padding: 6px 22px 11px;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: #1D9DD9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-buttons {
|
||||
padding: 10px 50px;
|
||||
|
||||
a:link, a:visited {
|
||||
padding: 15px 0px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
&#feedback_link_problem {
|
||||
border-bottom-style: none;
|
||||
@include border-radius(10px 10px 0px 0px);
|
||||
}
|
||||
|
||||
&#feedback_link_question {
|
||||
border-top-style: none;
|
||||
@include border-radius(0px 0px 10px 10px);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: #1D9DD9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#feedback_form textarea[name="details"] {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
display: block;
|
||||
color: #8F0E0E;
|
||||
|
||||
+ input {
|
||||
+ input, + textarea {
|
||||
border: 1px solid #CA1111;
|
||||
color: #8F0E0E;
|
||||
}
|
||||
|
||||
167
lms/templates/help_modal.html
Normal file
167
lms/templates/help_modal.html
Normal file
@@ -0,0 +1,167 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%! from django.conf import settings %>
|
||||
<%! from courseware.tabs import get_discussion_link %>
|
||||
|
||||
% if settings.MITX_FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
|
||||
|
||||
<div class="help-tab">
|
||||
<a href="#help-modal" rel="leanModal">Help</a>
|
||||
</div>
|
||||
|
||||
<section id="help-modal" class="modal">
|
||||
<div class="inner-wrapper" id="help_wrapper">
|
||||
<header>
|
||||
<h2><span class="edx">edX</span> Help</h2>
|
||||
<hr>
|
||||
</header>
|
||||
|
||||
<%
|
||||
discussion_link = get_discussion_link(course) if course else None
|
||||
%>
|
||||
% if discussion_link:
|
||||
<p>
|
||||
Have a course-specific question?
|
||||
<a href="${discussion_link}" target="_blank"/>
|
||||
Post it on the course forums.
|
||||
</a>
|
||||
</p>
|
||||
<hr>
|
||||
% endif
|
||||
|
||||
<p>Have a general question about edX? <a href="/help" target="_blank">Check the FAQ</a>.</p>
|
||||
<hr>
|
||||
|
||||
<div class="help-buttons">
|
||||
<a href="#" id="feedback_link_problem">Report a problem</a>
|
||||
<a href="#" id="feedback_link_suggestion">Make a suggestion</a>
|
||||
<a href="#" id="feedback_link_question">Ask a question</a>
|
||||
</div>
|
||||
|
||||
## TODO: find a way to refactor this
|
||||
<div class="close-modal">
|
||||
<div class="inner">
|
||||
<p>✕</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inner-wrapper" id="feedback_form_wrapper">
|
||||
<header></header>
|
||||
|
||||
<form id="feedback_form" class="feedback_form" method="post" data-remote="true" action="/submit_feedback">
|
||||
<div id="feedback_error" class="modal-form-error"></div>
|
||||
% if not user.is_authenticated():
|
||||
<label data-field="name">Name*</label>
|
||||
<input name="name" type="text">
|
||||
<label data-field="email">E-mail*</label>
|
||||
<input name="email" type="text">
|
||||
% endif
|
||||
<label data-field="subject">Subject*</label>
|
||||
<input name="subject" type="text">
|
||||
<label data-field="details">Details*</label>
|
||||
<textarea name="details"></textarea>
|
||||
<input name="tag" type="hidden">
|
||||
<div class="submit">
|
||||
<input name="submit" type="submit" value="Submit">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="close-modal">
|
||||
<div class="inner">
|
||||
<p>✕</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inner-wrapper" id="feedback_success_wrapper">
|
||||
<header>
|
||||
<h2>Thank You!</h2>
|
||||
<hr>
|
||||
</header>
|
||||
|
||||
<p>
|
||||
Thanks for your feedback. We will read your message, and our
|
||||
support team may contact you to respond or ask for further clarification.
|
||||
</p>
|
||||
|
||||
<div class="close-modal">
|
||||
<div class="inner">
|
||||
<p>✕</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
$(".help-tab").click(function() {
|
||||
$(".field-error").removeClass("field-error");
|
||||
$("#feedback_form")[0].reset();
|
||||
$("#feedback_form input[type='submit']").removeAttr("disabled");
|
||||
$("#feedback_form_wrapper").css("display", "none");
|
||||
$("#feedback_error").css("display", "none");
|
||||
$("#feedback_success_wrapper").css("display", "none");
|
||||
$("#help_wrapper").css("display", "block");
|
||||
});
|
||||
showFeedback = function(e, tag, title) {
|
||||
$("#help_wrapper").css("display", "none");
|
||||
$("#feedback_form input[name='tag']").val(tag);
|
||||
$("#feedback_form_wrapper").css("display", "block");
|
||||
$("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>");
|
||||
e.preventDefault();
|
||||
};
|
||||
$("#feedback_link_problem").click(function(e) {
|
||||
showFeedback(e, "problem", "Report a Problem");
|
||||
});
|
||||
$("#feedback_link_suggestion").click(function(e) {
|
||||
showFeedback(e, "suggestion", "Make a Suggestion");
|
||||
});
|
||||
$("#feedback_link_question").click(function(e) {
|
||||
showFeedback(e, "question", "Ask a Question");
|
||||
});
|
||||
$("#feedback_form").submit(function() {
|
||||
$("input[type='submit']", this).attr("disabled", "disabled");
|
||||
});
|
||||
$("#feedback_form").on("ajax:complete", function() {
|
||||
$("input[type='submit']", this).removeAttr("disabled");
|
||||
});
|
||||
$("#feedback_form").on("ajax:success", function(event, data, status, xhr) {
|
||||
$("#feedback_form_wrapper").css("display", "none");
|
||||
$("#feedback_success_wrapper").css("display", "block");
|
||||
});
|
||||
$("#feedback_form").on("ajax:error", function(event, xhr, status, error) {
|
||||
$(".field-error").removeClass("field-error");
|
||||
var responseData;
|
||||
try {
|
||||
responseData = jQuery.parseJSON(xhr.responseText);
|
||||
} catch(err) {
|
||||
}
|
||||
if (responseData) {
|
||||
$("[data-field='"+responseData.field+"']").addClass("field-error");
|
||||
$("#feedback_error").html(responseData.error).stop().css("display", "block");
|
||||
} else {
|
||||
// If no data (or malformed data) is returned, a server error occurred
|
||||
htmlStr = "An error has occurred.";
|
||||
% if settings.FEEDBACK_SUBMISSION_EMAIL:
|
||||
htmlStr += " Please <a href='#' id='feedback_email'>send us e-mail</a>.";
|
||||
% else:
|
||||
// If no email is configured, we can't do much other than
|
||||
// ask the user to try again later
|
||||
htmlStr += " Please try again later.";
|
||||
% endif
|
||||
$("#feedback_error").html(htmlStr).stop().css("display", "block");
|
||||
% if settings.FEEDBACK_SUBMISSION_EMAIL:
|
||||
$("#feedback_email").click(function(e) {
|
||||
mailto = "mailto:" + "${settings.FEEDBACK_SUBMISSION_EMAIL}" +
|
||||
"?subject=" + $("#feedback_form input[name='subject']").val() +
|
||||
"&body=" + $("#feedback_form textarea[name='details']").val();
|
||||
window.open(mailto);
|
||||
e.preventDefault();
|
||||
});
|
||||
%endif
|
||||
}
|
||||
});
|
||||
})(this)
|
||||
</script>
|
||||
|
||||
%endif
|
||||
@@ -96,3 +96,5 @@ site_status_msg = get_site_status_msg(course_id)
|
||||
<%include file="signup_modal.html" />
|
||||
<%include file="forgot_password_modal.html" />
|
||||
%endif
|
||||
|
||||
<%include file="help_modal.html"/>
|
||||
|
||||
@@ -114,8 +114,9 @@ urlpatterns = ('', # nopep8
|
||||
# Favicon
|
||||
(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}),
|
||||
|
||||
url(r'^submit_feedback$', 'util.views.submit_feedback_via_zendesk'),
|
||||
|
||||
# TODO: These urls no longer work. They need to be updated before they are re-enabled
|
||||
# url(r'^send_feedback$', 'util.views.send_feedback'),
|
||||
# url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user