Merge pull request #21316 from edx/BOM-70-1

BOM-70
This commit is contained in:
Ayub
2019-08-09 20:19:18 +05:00
committed by GitHub
13 changed files with 3 additions and 1119 deletions

View File

@@ -72,7 +72,6 @@ FEATURES: {AUTH_USE_OPENID_PROVIDER: true, CERTIFICATES_HTML_VIEW: true, CUSTOM_
ENABLE_CONTENT_LIBRARIES: true, ENABLE_DISCUSSION_SERVICE: true, ENABLE_EXTENDED_COURSE_DETAILS: true,
ENABLE_GRADE_DOWNLOADS: true, ENABLE_SPECIAL_EXAMS: true, ENTRANCE_EXAMS: true,
MILESTONES_APP: true, PREVIEW_LMS_BASE: 'preview.localhost:8003', SHOW_HEADER_LANGUAGE_SELECTOR: true}
FEEDBACK_SUBMISSION_EMAIL: ''
GITHUB_REPO_ROOT: '** OVERRIDDEN **'
GRADES_DOWNLOAD: {BUCKET: edx-grades, ROOT_PATH: /tmp/edx-s3/grades, STORAGE_TYPE: localfs}
JWT_AUTH: {JWT_PRIVATE_SIGNING_JWK: '{"e": "AQAB", "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ",

View File

@@ -77,7 +77,6 @@
"ENABLE_EXTENDED_COURSE_DETAILS": true,
"CUSTOM_COURSES_EDX": true
},
"FEEDBACK_SUBMISSION_EMAIL": "",
"GITHUB_REPO_ROOT": "** OVERRIDDEN **",
"GRADES_DOWNLOAD": {
"BUCKET": "edx-grades",

View File

@@ -72,7 +72,6 @@ FEATURES: {AUTH_USE_OPENID_PROVIDER: true, CERTIFICATES_HTML_VIEW: true, CUSTOM_
ENABLE_CONTENT_LIBRARIES: true, ENABLE_DISCUSSION_SERVICE: true, ENABLE_EXTENDED_COURSE_DETAILS: true,
ENABLE_GRADE_DOWNLOADS: true, ENABLE_SPECIAL_EXAMS: true, ENTRANCE_EXAMS: true,
MILESTONES_APP: true, PREVIEW_LMS_BASE: 'preview.localhost:8003', SHOW_HEADER_LANGUAGE_SELECTOR: true}
FEEDBACK_SUBMISSION_EMAIL: ''
GITHUB_REPO_ROOT: '** OVERRIDDEN **'
GRADES_DOWNLOAD: {BUCKET: edx-grades, ROOT_PATH: /tmp/edx-s3/grades, STORAGE_TYPE: localfs}
JWT_AUTH: {JWT_PRIVATE_SIGNING_JWK: '{"e": "AQAB", "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ",

View File

@@ -1,617 +0,0 @@
"""Tests for the Zendesk"""
from __future__ import absolute_import
import json
from smtplib import SMTPException
import httpretty
import mock
from ddt import data, ddt, unpack
from django.contrib.auth.models import AnonymousUser
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from zendesk import ZendeskError
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from student.tests.test_configuration_overrides import fake_get_value
from util import views
TEST_SUPPORT_EMAIL = "support@example.com"
TEST_ZENDESK_CUSTOM_FIELD_CONFIG = {
"course_id": 1234,
"enrollment_mode": 5678,
'enterprise_customer_name': 'enterprise_customer_name'
}
TEST_REQUEST_HEADERS = {
"HTTP_REFERER": "test_referer",
"HTTP_USER_AGENT": "test_user_agent",
"REMOTE_ADDR": "1.2.3.4",
"SERVER_NAME": "test_server",
}
def fake_support_backend_values(name, default=None): # pylint: disable=unused-argument
"""
Method for getting configuration override values for support email.
"""
config_dict = {
"CONTACT_FORM_SUBMISSION_BACKEND": "email",
"email_from_address": TEST_SUPPORT_EMAIL,
}
return config_dict[name]
@ddt
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_FEEDBACK_SUBMISSION": True})
@override_settings(
DEFAULT_FROM_EMAIL=TEST_SUPPORT_EMAIL,
ZENDESK_URL="dummy",
ZENDESK_USER="dummy",
ZENDESK_API_KEY="dummy",
ZENDESK_CUSTOM_FIELDS={},
)
@mock.patch("util.views._ZendeskApi", autospec=True)
class SubmitFeedbackTest(EnterpriseServiceMockMixin, TestCase):
"""
Class to test the submit_feedback function in views.
"""
def setUp(self):
"""Set up data for the test case"""
super(SubmitFeedbackTest, self).setUp()
self._request_factory = RequestFactory()
self._anon_user = AnonymousUser()
self._auth_user = UserFactory.create(
email="test@edx.org",
username="test",
profile__name="Test User"
)
self._anon_fields = {
"email": "test@edx.org",
"name": "Test User",
"subject": "a subject",
"details": "some details",
"issue_type": "test_issue"
}
# This does not contain issue_type nor course_id to ensure that they are optional
self._auth_fields = {"subject": "a subject", "details": "some details"}
# Create a service user, because the track selection page depends on it
UserFactory.create(
username='enterprise_worker',
email="enterprise_worker@example.com",
password="edx",
)
def _build_and_run_request(self, user, fields):
"""
Generate a request and invoke the view, returning the response.
The request will be a POST request from the given `user`, with the given
`fields` in the POST body.
"""
req = self._request_factory.post(
"/submit_feedback",
data=fields,
HTTP_REFERER=TEST_REQUEST_HEADERS["HTTP_REFERER"],
HTTP_USER_AGENT=TEST_REQUEST_HEADERS["HTTP_USER_AGENT"],
REMOTE_ADDR=TEST_REQUEST_HEADERS["REMOTE_ADDR"],
SERVER_NAME=TEST_REQUEST_HEADERS["SERVER_NAME"],
)
req.site = SiteFactory.create()
req.user = user
return views.submit_feedback(req)
def _assert_bad_request(self, response, field, zendesk_mock_class):
"""
Assert that the given `response` contains correct failure data.
It should have a 400 status code, and its content should be a JSON
object containing the specified `field` and an `error`.
"""
self.assertEqual(response.status_code, 400)
resp_json = json.loads(response.content)
self.assertIn("field", resp_json)
self.assertEqual(resp_json["field"], field)
self.assertIn("error", resp_json)
# There should be absolutely no interaction with Zendesk
self.assertFalse(zendesk_mock_class.return_value.mock_calls)
def _test_bad_request_omit_field(self, user, fields, omit_field, zendesk_mock_class):
"""
Invoke the view with a request missing a field and assert correctness.
The request will be a POST request from the given `user`, with POST
fields taken from `fields` minus the entry specified by `omit_field`.
The response should have a 400 (bad request) status code and specify
the invalid field and an error message, and the Zendesk API should not
have been invoked.
"""
filtered_fields = {k: v for (k, v) in fields.items() if k != omit_field}
resp = self._build_and_run_request(user, filtered_fields)
self._assert_bad_request(resp, omit_field, zendesk_mock_class)
def _test_bad_request_empty_field(self, user, fields, empty_field, zendesk_mock_class):
"""
Invoke the view with an empty field and assert correctness.
The request will be a POST request from the given `user`, with POST
fields taken from `fields`, replacing the entry specified by
`empty_field` with the empty string. The response should have a 400
(bad request) status code and specify the invalid field and an error
message, and the Zendesk API should not have been invoked.
"""
altered_fields = fields.copy()
altered_fields[empty_field] = ""
resp = self._build_and_run_request(user, altered_fields)
self._assert_bad_request(resp, empty_field, zendesk_mock_class)
def _test_success(self, user, fields):
"""
Generate a request, invoke the view, and assert success.
The request will be a POST request from the given `user`, with the given
`fields` in the POST body. The response should have a 200 (success)
status code.
"""
resp = self._build_and_run_request(user, fields)
self.assertEqual(resp.status_code, 200)
def _build_zendesk_ticket(self, recipient, name, email, subject, details, tags, custom_fields=None):
"""
Build a Zendesk ticket that can be used in assertions to verify that the correct
data was submitted to create a Zendesk ticket.
"""
ticket = {
"ticket": {
"recipient": recipient,
"requester": {"name": name, "email": email},
"subject": subject,
"comment": {"body": details},
"tags": tags
}
}
if custom_fields is not None:
ticket["ticket"]["custom_fields"] = custom_fields
return ticket
def _build_zendesk_ticket_update(self, request_headers, username=None):
"""
Build a Zendesk ticket update that can be used in assertions to verify that the correct
data was submitted to update a Zendesk ticket.
"""
body = []
if username:
body.append("username: {}".format(username))
# FIXME the tests rely on the body string being built in this specific order, which doesn't seem
# reliable given that the view builds the string by iterating over a dictionary.
header_text_mapping = [
("Client IP", "REMOTE_ADDR"),
("Host", "SERVER_NAME"),
("Page", "HTTP_REFERER"),
("Browser", "HTTP_USER_AGENT")
]
for text, header in header_text_mapping:
body.append("{}: {}".format(text, request_headers[header]))
body = "Additional information:\n\n" + "\n".join(body)
return {"ticket": {"comment": {"public": False, "body": body}}}
def _assert_zendesk_called(self, zendesk_mock, ticket_id, ticket, ticket_update):
"""Assert that Zendesk was called with the correct ticket and ticket_update."""
expected_zendesk_calls = [mock.call.create_ticket(ticket), mock.call.update_ticket(ticket_id, ticket_update)]
self.assertEqual(zendesk_mock.mock_calls, expected_zendesk_calls)
def test_bad_request_anon_user_no_name(self, zendesk_mock_class):
"""Test a request from an anonymous user not specifying `name`."""
self._test_bad_request_omit_field(self._anon_user, self._anon_fields, "name", zendesk_mock_class)
self._test_bad_request_empty_field(self._anon_user, self._anon_fields, "name", zendesk_mock_class)
def test_bad_request_anon_user_no_email(self, zendesk_mock_class):
"""Test a request from an anonymous user not specifying `email`."""
self._test_bad_request_omit_field(self._anon_user, self._anon_fields, "email", zendesk_mock_class)
self._test_bad_request_empty_field(self._anon_user, self._anon_fields, "email", zendesk_mock_class)
def test_bad_request_anon_user_invalid_email(self, zendesk_mock_class):
"""Test a request from an anonymous user specifying an invalid `email`."""
fields = self._anon_fields.copy()
fields["email"] = "This is not a valid email address!"
resp = self._build_and_run_request(self._anon_user, fields)
self._assert_bad_request(resp, "email", zendesk_mock_class)
def test_bad_request_anon_user_no_subject(self, zendesk_mock_class):
"""Test a request from an anonymous user not specifying `subject`."""
self._test_bad_request_omit_field(self._anon_user, self._anon_fields, "subject", zendesk_mock_class)
self._test_bad_request_empty_field(self._anon_user, self._anon_fields, "subject", zendesk_mock_class)
def test_bad_request_anon_user_no_details(self, zendesk_mock_class):
"""Test a request from an anonymous user not specifying `details`."""
self._test_bad_request_omit_field(self._anon_user, self._anon_fields, "details", zendesk_mock_class)
self._test_bad_request_empty_field(self._anon_user, self._anon_fields, "details", zendesk_mock_class)
def test_valid_request_anon_user(self, zendesk_mock_class):
"""
Test a valid request from an anonymous user.
The response should have a 200 (success) status code, and a ticket with
the given information should have been submitted via the Zendesk API.
"""
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._anon_user
fields = self._anon_fields
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
ticket = self._build_zendesk_ticket(
recipient=TEST_SUPPORT_EMAIL,
name=fields["name"],
email=fields["email"],
subject=fields["subject"],
details=fields["details"],
tags=[fields["issue_type"], "LMS"]
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
@mock.patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_get_value)
def test_valid_request_anon_user_configuration_override(self, zendesk_mock_class):
"""
Test a valid request from an anonymous user to a mocked out site with configuration override
The response should have a 200 (success) status code, and a ticket with
the given information should have been submitted via the Zendesk API with the additional
tag that will come from site configuration override.
"""
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._anon_user
fields = self._anon_fields
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
ticket = self._build_zendesk_ticket(
recipient=fake_get_value("email_from_address"),
name=fields["name"],
email=fields["email"],
subject=fields["subject"],
details=fields["details"],
tags=[fields["issue_type"], "LMS", "site_name_{}".format(fake_get_value("SITE_NAME").replace(".", "_"))]
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
@data("course-v1:testOrg+testCourseNumber+testCourseRun", "", None)
@override_settings(ZENDESK_CUSTOM_FIELDS=TEST_ZENDESK_CUSTOM_FIELD_CONFIG)
def test_valid_request_anon_user_with_custom_fields(self, course_id, zendesk_mock_class):
"""
Test a valid request from an anonymous user when configured to use Zendesk Custom Fields.
The response should have a 200 (success) status code, and a ticket with
the given information should have been submitted via the Zendesk API. When course_id is
present, it should be sent to Zendesk via a custom field. When course_id is blank or missing,
the request should still be processed successfully.
"""
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._anon_user
fields = self._anon_fields.copy()
if course_id is not None:
fields["course_id"] = course_id
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
zendesk_tags = [fields["issue_type"], "LMS"]
zendesk_custom_fields = None
if course_id:
# FIXME the tests rely on the tags being in this specific order, which doesn't seem
# reliable given that the view builds the list by iterating over a dictionary.
zendesk_tags.insert(0, course_id)
zendesk_custom_fields = [
{"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["course_id"], "value": course_id}
]
ticket = self._build_zendesk_ticket(
recipient=TEST_SUPPORT_EMAIL,
name=fields["name"],
email=fields["email"],
subject=fields["subject"],
details=fields["details"],
tags=zendesk_tags,
custom_fields=zendesk_custom_fields
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
def test_bad_request_auth_user_no_subject(self, zendesk_mock_class):
"""Test a request from an authenticated user not specifying `subject`."""
self._test_bad_request_omit_field(self._auth_user, self._auth_fields, "subject", zendesk_mock_class)
self._test_bad_request_empty_field(self._auth_user, self._auth_fields, "subject", zendesk_mock_class)
def test_bad_request_auth_user_no_details(self, zendesk_mock_class):
"""Test a request from an authenticated user not specifying `details`."""
self._test_bad_request_omit_field(self._auth_user, self._auth_fields, "details", zendesk_mock_class)
self._test_bad_request_empty_field(self._auth_user, self._auth_fields, "details", zendesk_mock_class)
def test_valid_request_auth_user(self, zendesk_mock_class):
"""
Test a valid request from an authenticated user.
The response should have a 200 (success) status code, and a ticket with
the given information should have been submitted via the Zendesk API.
"""
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._auth_user
fields = self._auth_fields
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
ticket = self._build_zendesk_ticket(
recipient=TEST_SUPPORT_EMAIL,
name=user.profile.name,
email=user.email,
subject=fields["subject"],
details=fields["details"],
tags=["LMS"]
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS, user.username)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
@data(
("course-v1:testOrg+testCourseNumber+testCourseRun", True),
("course-v1:testOrg+testCourseNumber+testCourseRun", False),
("", None),
(None, None)
)
@unpack
@override_settings(ZENDESK_CUSTOM_FIELDS=TEST_ZENDESK_CUSTOM_FIELD_CONFIG)
def test_valid_request_auth_user_with_custom_fields(self, course_id, enrolled, zendesk_mock_class):
"""
Test a valid request from an authenticated user when configured to use Zendesk Custom Fields.
The response should have a 200 (success) status code, and a ticket with
the given information should have been submitted via the Zendesk API. When course_id is
present, it should be sent to Zendesk via a custom field, along with the enrollment mode
if the user has an active enrollment for that course. When course_id is blank or missing,
the request should still be processed successfully.
"""
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._auth_user
fields = self._auth_fields.copy()
if course_id is not None:
fields["course_id"] = course_id
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
zendesk_tags = ["LMS"]
zendesk_custom_fields = None
if course_id:
# FIXME the tests rely on the tags being in this specific order, which doesn't seem
# reliable given that the view builds the list by iterating over a dictionary.
zendesk_tags.insert(0, course_id)
zendesk_custom_fields = [
{"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["course_id"], "value": course_id}
]
if enrolled is not None:
enrollment = CourseEnrollmentFactory.create(
user=user,
course_id=course_id,
is_active=enrolled
)
if enrollment.is_active:
zendesk_custom_fields.append(
{"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["enrollment_mode"], "value": enrollment.mode}
)
ticket = self._build_zendesk_ticket(
recipient=TEST_SUPPORT_EMAIL,
name=user.profile.name,
email=user.email,
subject=fields["subject"],
details=fields["details"],
tags=zendesk_tags,
custom_fields=zendesk_custom_fields
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS, user.username)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
@httpretty.activate
@data(
("course-v1:testOrg+testCourseNumber+testCourseRun", True),
("course-v1:testOrg+testCourseNumber+testCourseRun", False),
)
@unpack
@override_settings(ZENDESK_CUSTOM_FIELDS=TEST_ZENDESK_CUSTOM_FIELD_CONFIG)
@mock.patch.dict("django.conf.settings.FEATURES", dict(ENABLE_ENTERPRISE_INTEGRATION=True))
def test_valid_request_auth_user_with_enterprise_info(self, course_id, enrolled, zendesk_mock_class):
"""
Test a valid request from an authenticated user with enterprise tags.
"""
self.mock_enterprise_learner_api()
zendesk_mock_instance = zendesk_mock_class.return_value
user = self._auth_user
fields = self._auth_fields.copy()
if course_id is not None:
fields["course_id"] = course_id
ticket_id = 42
zendesk_mock_instance.create_ticket.return_value = ticket_id
zendesk_tags = ["enterprise_learner", "LMS"]
zendesk_custom_fields = []
if course_id:
zendesk_tags.insert(0, course_id)
zendesk_custom_fields.append({"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["course_id"], "value": course_id})
if enrolled is not None:
enrollment = CourseEnrollmentFactory.create(
user=user,
course_id=course_id,
is_active=enrolled
)
if enrollment.is_active:
zendesk_custom_fields.append(
{"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["enrollment_mode"], "value": enrollment.mode}
)
zendesk_custom_fields.append(
{
"id": TEST_ZENDESK_CUSTOM_FIELD_CONFIG["enterprise_customer_name"],
"value": 'TestShib'
}
)
ticket = self._build_zendesk_ticket(
recipient=TEST_SUPPORT_EMAIL,
name=user.profile.name,
email=user.email,
subject=fields["subject"],
details=fields["details"],
tags=zendesk_tags,
custom_fields=zendesk_custom_fields
)
ticket_update = self._build_zendesk_ticket_update(TEST_REQUEST_HEADERS, user.username)
self._test_success(user, fields)
self._assert_zendesk_called(zendesk_mock_instance, ticket_id, ticket, ticket_update)
@httpretty.activate
@override_settings(ZENDESK_CUSTOM_FIELDS=TEST_ZENDESK_CUSTOM_FIELD_CONFIG)
@mock.patch.dict("django.conf.settings.FEATURES", dict(ENABLE_ENTERPRISE_INTEGRATION=True))
def test_request_with_anonymous_user_without_enterprise_info(self, zendesk_mock_class):
"""
Test tags related to enterprise should not be there in case an unauthenticated user.
"""
ticket_id = 42
self.mock_enterprise_learner_api()
user = self._anon_user
zendesk_mock_instance = zendesk_mock_class.return_value
zendesk_mock_instance.create_ticket.return_value = ticket_id
resp = self._build_and_run_request(user, self._anon_fields)
self.assertEqual(resp.status_code, 200)
@httpretty.activate
@override_settings(ZENDESK_CUSTOM_FIELDS=TEST_ZENDESK_CUSTOM_FIELD_CONFIG)
@mock.patch.dict("django.conf.settings.FEATURES", dict(ENABLE_ENTERPRISE_INTEGRATION=True))
def test_tags_in_request_with_auth_user_with_enterprise_info(self, zendesk_mock_class):
"""
Test tags related to enterprise should be there in case the request is generated by an authenticated user.
"""
ticket_id = 42
self.mock_enterprise_learner_api()
user = self._auth_user
zendesk_mock_instance = zendesk_mock_class.return_value
zendesk_mock_instance.create_ticket.return_value = ticket_id
resp = self._build_and_run_request(user, self._auth_fields)
self.assertEqual(resp.status_code, 200)
def test_get_request(self, zendesk_mock_class):
"""Test that a GET results in a 405 even with all required fields"""
req = self._request_factory.get("/submit_feedback", data=self._anon_fields)
req.user = self._anon_user
resp = views.submit_feedback(req)
self.assertEqual(resp.status_code, 405)
self.assertIn("Allow", resp)
self.assertEqual(resp["Allow"], "POST")
# There should be absolutely no interaction with Zendesk
self.assertFalse(zendesk_mock_class.mock_calls)
def test_zendesk_error_on_create(self, zendesk_mock_class):
"""
Test Zendesk returning an error on ticket creation.
We should return a 500 error with no body
"""
err = ZendeskError(msg="", error_code=404)
zendesk_mock_instance = zendesk_mock_class.return_value
zendesk_mock_instance.create_ticket.side_effect = err
resp = self._build_and_run_request(self._anon_user, self._anon_fields)
self.assertEqual(resp.status_code, 500)
self.assertFalse(resp.content)
def test_zendesk_error_on_update(self, zendesk_mock_class):
"""
Test for Zendesk returning an error on ticket update.
If Zendesk returns any error on ticket update, we return a 200 to the
browser because the update contains additional information that is not
necessary for the user to have submitted their feedback.
"""
err = ZendeskError(msg="", error_code=500)
zendesk_mock_instance = zendesk_mock_class.return_value
zendesk_mock_instance.update_ticket.side_effect = err
resp = self._build_and_run_request(self._anon_user, self._anon_fields)
self.assertEqual(resp.status_code, 200)
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_FEEDBACK_SUBMISSION": False})
def test_not_enabled(self, zendesk_mock_class):
"""
Test for Zendesk submission not enabled in `settings`.
We should raise Http404.
"""
with self.assertRaises(Http404):
self._build_and_run_request(self._anon_user, self._anon_fields)
def test_zendesk_not_configured(self, zendesk_mock_class):
"""
Test for Zendesk not fully configured in `settings`.
For each required configuration parameter, test that setting it to
`None` causes an otherwise valid request to return a 500 error.
"""
def test_case(missing_config):
with mock.patch(missing_config, None):
with self.assertRaises(Exception):
self._build_and_run_request(self._anon_user, self._anon_fields)
test_case("django.conf.settings.ZENDESK_URL")
test_case("django.conf.settings.ZENDESK_USER")
test_case("django.conf.settings.ZENDESK_API_KEY")
@mock.patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_support_backend_values)
def test_valid_request_over_email(self, zendesk_mock_class): # pylint: disable=unused-argument
with mock.patch("util.views.send_mail") as patched_send_email:
resp = self._build_and_run_request(self._anon_user, self._anon_fields)
self.assertEqual(patched_send_email.call_count, 1)
self.assertIn(self._anon_fields["email"], str(patched_send_email.call_args))
self.assertEqual(resp.status_code, 200)
@mock.patch("openedx.core.djangoapps.site_configuration.helpers.get_value", fake_support_backend_values)
def test_exception_request_over_email(self, zendesk_mock_class): # pylint: disable=unused-argument
with mock.patch("util.views.send_mail", side_effect=SMTPException) as patched_send_email:
resp = self._build_and_run_request(self._anon_user, self._anon_fields)
self.assertEqual(patched_send_email.call_count, 1)
self.assertIn(self._anon_fields["email"], str(patched_send_email.call_args))
self.assertEqual(resp.status_code, 500)

View File

@@ -4,7 +4,6 @@ import json
import logging
import sys
from functools import wraps
from smtplib import SMTPException
import calc
import crum
@@ -12,9 +11,7 @@ import zendesk
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.cache import caches
from django.core.mail import send_mail
from django.core.validators import ValidationError, validate_email
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseServerError
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.views.decorators.csrf import requires_csrf_token
from django.views.defaults import server_error
from opaque_keys import InvalidKeyError
@@ -22,18 +19,13 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
from six.moves import map
import track.views
from edxmako.shortcuts import render_to_response, render_to_string
from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.features.enterprise_support import api as enterprise_api
from student.models import CourseEnrollment
from student.roles import GlobalStaff
log = logging.getLogger(__name__)
DATADOG_FEEDBACK_METRIC = "lms_feedback_submissions"
SUPPORT_BACKEND_ZENDESK = "support_ticket"
SUPPORT_BACKEND_EMAIL = "email"
def ensure_valid_course_key(view_func):
"""
@@ -402,99 +394,6 @@ def get_feedback_form_context(request):
return context
def submit_feedback(request):
"""
Create a Zendesk ticket or if not available, send an email with the
feedback form fields.
If feedback submission is not enabled, any request will raise `Http404`.
If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or
`ZENDESK_API_KEY`) is missing, any request will raise an `Exception`.
The request must be a POST request specifying `subject` and `details`.
If the user is not authenticated, the request must also specify `name` and
`email`. If the user is authenticated, the `name` and `email` will be
populated from the user's information. If any required parameter is
missing, a 400 error will be returned indicating which field is missing and
providing an error message. If Zendesk ticket creation fails, 500 error
will be returned with no body; if ticket creation succeeds, an empty
successful response (200) will be returned.
"""
if not settings.FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
raise Http404()
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
def build_error_response(status_code, field, err_msg):
return HttpResponse(json.dumps({"field": field, "error": err_msg}), status=status_code)
required_fields = ["subject", "details"]
if not request.user.is_authenticated:
required_fields += ["name", "email"]
required_field_errs = {
"subject": "Please provide a subject.",
"details": "Please provide details.",
"name": "Please provide your name.",
"email": "Please provide a valid e-mail.",
}
for field in required_fields:
if field not in request.POST or not request.POST[field]:
return build_error_response(400, field, required_field_errs[field])
if not request.user.is_authenticated:
try:
validate_email(request.POST["email"])
except ValidationError:
return build_error_response(400, "email", required_field_errs["email"])
success = False
context = get_feedback_form_context(request)
# Update the tag info with 'enterprise_learner' if the user belongs to an enterprise customer.
enterprise_learner_data = enterprise_api.get_enterprise_learner_data(user=request.user)
if enterprise_learner_data:
context["tags"]["learner_type"] = "enterprise_learner"
support_backend = configuration_helpers.get_value('CONTACT_FORM_SUBMISSION_BACKEND', SUPPORT_BACKEND_ZENDESK)
if support_backend == SUPPORT_BACKEND_EMAIL:
try:
send_mail(
subject=render_to_string('emails/contact_us_feedback_email_subject.txt', context),
message=render_to_string('emails/contact_us_feedback_email_body.txt', context),
from_email=context["support_email"],
recipient_list=[context["support_email"]],
fail_silently=False
)
success = True
except SMTPException:
log.exception('Error sending feedback to contact_us email address.')
success = False
else:
if not settings.ZENDESK_URL or not settings.ZENDESK_USER or not settings.ZENDESK_API_KEY:
raise Exception("Zendesk enabled but not configured")
custom_fields = None
if settings.ZENDESK_CUSTOM_FIELDS:
custom_field_context = _get_zendesk_custom_field_context(request, learner_data=enterprise_learner_data)
custom_fields = _format_zendesk_custom_fields(custom_field_context)
success = _record_feedback_in_zendesk(
context["realname"],
context["email"],
context["subject"],
context["details"],
context["tags"],
context["additional_info"],
support_email=context["support_email"],
custom_fields=custom_fields
)
return HttpResponse(status=(200 if success else 500))
def info(request):
''' Info page (link from main header) '''
# pylint: disable=unused-argument

View File

@@ -84,7 +84,6 @@ FEATURES: {ALLOW_AUTOMATED_SIGNUPS: true, AUTH_USE_OPENID_PROVIDER: true, AUTOMA
ENABLE_PAYMENT_FAKE: true, ENABLE_SPECIAL_EXAMS: true, ENABLE_THIRD_PARTY_AUTH: true,
ENABLE_VERIFIED_CERTIFICATES: true, EXPOSE_CACHE_PROGRAMS_ENDPOINT: true, MODE_CREATION_FOR_TESTING: true,
PREVIEW_LMS_BASE: 'preview.localhost:8003', RESTRICT_AUTOMATIC_AUTH: false, SHOW_HEADER_LANGUAGE_SELECTOR: true}
FEEDBACK_SUBMISSION_EMAIL: ''
GITHUB_REPO_ROOT: '** OVERRIDDEN **'
JWT_AUTH: {JWT_PRIVATE_SIGNING_JWK: '{"e": "AQAB", "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ",
"n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ",

View File

@@ -88,7 +88,6 @@ FEATURES: {ALLOW_AUTOMATED_SIGNUPS: true, AUTH_USE_OPENID_PROVIDER: true, AUTOMA
ENABLE_PAYMENT_FAKE: true, ENABLE_SPECIAL_EXAMS: true, ENABLE_THIRD_PARTY_AUTH: true,
ENABLE_VERIFIED_CERTIFICATES: true, EXPOSE_CACHE_PROGRAMS_ENDPOINT: true, MODE_CREATION_FOR_TESTING: true,
PREVIEW_LMS_BASE: 'preview.localhost:8003', RESTRICT_AUTOMATIC_AUTH: false, SHOW_HEADER_LANGUAGE_SELECTOR: true}
FEEDBACK_SUBMISSION_EMAIL: ''
GITHUB_REPO_ROOT: '** OVERRIDDEN **'
JWT_AUTH: {JWT_PRIVATE_SIGNING_JWK: '{"e": "AQAB", "d": "HIiV7KNjcdhVbpn3KT-I9n3JPf5YbGXsCIedmPqDH1d4QhBofuAqZ9zebQuxkRUpmqtYMv0Zi6ECSUqH387GYQF_XvFUFcjQRPycISd8TH0DAKaDpGr-AYNshnKiEtQpINhcP44I1AYNPCwyoxXA1fGTtmkKChsuWea7o8kytwU5xSejvh5-jiqu2SF4GEl0BEXIAPZsgbzoPIWNxgO4_RzNnWs6nJZeszcaDD0CyezVSuH9QcI6g5QFzAC_YuykSsaaFJhZ05DocBsLczShJ9Omf6PnK9xlm26I84xrEh_7x4fVmNBg3xWTLh8qOnHqGko93A1diLRCrKHOvnpvgQ",
"n": "o5cn3ljSRi6FaDEKTn0PS-oL9EFyv1pI7dRgffQLD1qf5D6sprmYfWWokSsrWig8u2y0HChSygR6Jn5KXBqQn6FpM0dDJLnWQDRXHLl3Ey1iPYgDSmOIsIGrV9ZyNCQwk03wAgWbfdBTig3QSDYD-sTNOs3pc4UD_PqAvU2nz_1SS2ZiOwOn5F6gulE1L0iE3KEUEvOIagfHNVhz0oxa_VRZILkzV-zr6R_TW1m97h4H8jXl_VJyQGyhMGGypuDrQ9_vaY_RLEulLCyY0INglHWQ7pckxBtI5q55-Vio2wgewe2_qYcGsnBGaDNbySAsvYcWRrqDiFyzrJYivodqTQ",

View File

@@ -167,9 +167,6 @@ FEATURES = {
# Staff Debug tool.
'ENABLE_STUDENT_HISTORY_VIEW': True,
# Provide a UI to allow users to submit feedback from the LMS (left-hand help modal)
'ENABLE_FEEDBACK_SUBMISSION': False,
# Turn on a page that lets staff enter Python code to be run in the
# sandbox, for testing whether it's enabled properly.
'ENABLE_DEBUG_RUN_PYTHON': False,
@@ -1347,9 +1344,6 @@ WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
WIKI_LINK_LIVE_LOOKUPS = False
WIKI_LINK_DEFAULT_LEVEL = 2
##### Feedback submission mechanism #####
FEEDBACK_SUBMISSION_EMAIL = None
##### Zendesk #####
ZENDESK_URL = ''
ZENDESK_USER = ''

View File

@@ -371,7 +371,6 @@ CERT_QUEUE = ENV_TOKENS.get("CERT_QUEUE", 'test-pull')
ZENDESK_URL = ENV_TOKENS.get('ZENDESK_URL', ZENDESK_URL)
ZENDESK_CUSTOM_FIELDS = ENV_TOKENS.get('ZENDESK_CUSTOM_FIELDS', ZENDESK_CUSTOM_FIELDS)
FEEDBACK_SUBMISSION_EMAIL = ENV_TOKENS.get("FEEDBACK_SUBMISSION_EMAIL")
MKTG_URLS = ENV_TOKENS.get('MKTG_URLS', MKTG_URLS)
# Badgr API

View File

@@ -1,358 +0,0 @@
<%page expression_filter="h"/>
<%namespace name='static' file='static_content.html'/>
<%!
from datetime import datetime
import pytz
from django.conf import settings
from django.utils.translation import ugettext as _
from django.urls import reverse
from openedx.core.djangolib.js_utils import js_escaped_string
from openedx.core.djangolib.markup import HTML, Text
from xmodule.tabs import CourseTabList
%>
% if settings.FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
<div class="help-tab hidden-mobile">
<a href="#help-modal" rel="leanModal" role="button">${_("Support")}</a>
</div>
<div id="help-modal" class="modal" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1" aria-labelledby="support-platform-name">
<div class="inner-wrapper">
## TODO: find a way to refactor this
<button class="btn-link close-modal" tabindex="0">
<span class="icon fa fa-remove" aria-hidden="true"></span>
<span class="sr">
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')}
</span>
</button>
<div id="help_wrapper">
<header>
<h2 id="support-platform-name">
${Text(_('{platform_name} Support')).format(
platform_name=HTML(u'<span class="edx">{}</span>').format(static.get_platform_name())
)}
</h2>
<hr>
</header>
<%
discussion_tab = CourseTabList.get_discussion(course) if course else None
discussion_link = discussion_tab.link_func(course, reverse) if (discussion_tab and discussion_tab.is_enabled(course, user=user)) else None
%>
% if discussion_link:
<p>${Text(_('For {strong_start}questions on course lectures, homework, tools, or materials for this course{strong_end}, post in the {link_start}course discussion forum{link_end}.')).format(
strong_start=HTML('<strong>'),
strong_end=HTML('</strong>'),
link_start=HTML('<a href="{url}" target="_blank">').format(
url=discussion_link
),
link_end=HTML('</a>'),
)}
</p>
% endif
<p>${Text(_('Have {strong_start}general questions about {platform_name}{strong_end}? You can find lots of helpful information in the {platform_name} {link_start}FAQ{link_end}.')).format(
strong_start=HTML('<strong>'),
strong_end=HTML('</strong>'),
link_start=HTML('<a href="{url}" id="feedback-faq-link" target="_blank">').format(
url=marketing_link('FAQ')
),
link_end=HTML('</a>'),
platform_name=static.get_platform_name())}
</p>
<p>${Text(_('Have a {strong_start}question about something specific{strong_end}? You can contact the {platform_name} general support team directly:')).format(
strong_start=HTML('<strong>'),
strong_end=HTML('</strong>'),
platform_name=static.get_platform_name()
)}</p>
<hr>
<div class="help-buttons">
<button type="button" class="btn btn-outline-primary" id="feedback_link_problem">${_('Report a problem')}</button>
<button type="button" class="btn btn-outline-primary" id="feedback_link_suggestion">${_('Make a suggestion')}</button>
<button type="button" class="btn btn-outline-primary" id="feedback_link_question">${_('Ask a question')}</button>
</div>
<p class="note">${_('Please note: The {platform_name} support team is English speaking. While we will do our best to address your inquiry in any language, our responses will be in English.').format(
platform_name=static.get_platform_name()
)}</p>
</div>
<div id="feedback_form_wrapper">
<header></header>
<form id="feedback_form" class="feedback_form" method="post" data-remote="true" action="/submit_feedback">
<div class="feedback-form-error" aria-live="polite">
<div id="feedback_error" class="modal-form-error" tabindex="-1"></div>
</div>
% if not user.is_authenticated:
<label data-field="name" for="feedback_form_name">${_('Name')}*</label>
<input name="name" type="text" id="feedback_form_name" required>
<label data-field="email" for="feedback_form_email">${_('E-mail')}*</label>
<input name="email" type="text" id="feedback_form_email" required>
% endif
<div class="js-course-id-anchor">
% if course:
<input name="course_id" type="hidden" value="${unicode(course.id)}">
% endif
</div>
<label data-field="subject" for="feedback_form_subject">${_('Briefly describe your issue')}*</label>
<input name="subject" type="text" id="feedback_form_subject" required>
<label data-field="details" for="feedback_form_details">${_('Tell us the details')}*</label>
<span class="tip" id="feedback_form_details_tip">${_('Describe what you were doing when you encountered the issue. Include any details that will help us to troubleshoot, including error messages that you saw.')}</span>
<textarea name="details" id="feedback_form_details" required aria-describedby="feedback_form_details_tip"></textarea>
<input name="issue_type" type="hidden">
<div class="submit">
<input name="submit" type="submit" value="${_('Submit')}" id="feedback_submit">
</div>
</form>
</div>
<div id="feedback_success_wrapper">
<header>
<h2>${_('Thank You!')}</h2>
<hr>
</header>
<p>
${Text(_(
'Thank you for your inquiry or feedback. We typically respond to a request '
'within one business day, Monday to Friday. In the meantime, please '
'review our {link_start}detailed FAQs{link_end} where most questions have '
'already been answered.'
)).format(
link_start=HTML('<a href="{}" target="_blank" id="success-feedback-faq-link">').format(marketing_link('FAQ')),
link_end=HTML('</a>')
)}
</p>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
var currentCourseId,
courseOptions = [],
userAuthenticated = false,
courseOptionsLoadInProgress = false,
finishedLoadingCourseOptions = false,
$helpModal = $("#help-modal"),
$closeButton = $("#help-modal .close-modal"),
$leanOverlay = $("#lean_overlay"),
$feedbackForm = $("#feedback_form"),
onModalClose = function() {
$closeButton.off("click");
$leanOverlay.off("click");
$helpModal.attr("aria-hidden", "true");
$('area,input,select,textarea,button').removeAttr('tabindex');
$(".help-tab a").focus();
$leanOverlay.removeAttr('tabindex');
},
showFeedback = function(event, issue_type, title, subject_label, details_label) {
event.preventDefault();
DialogTabControls.initializeTabKeyValues("#feedback_form_wrapper", $closeButton);
$("#feedback_form input[name='issue_type']").val(issue_type);
$("#feedback_form_wrapper header").html("<h2>" + title + "</h2><hr>");
$("#feedback_form_wrapper label[data-field='subject']").html(subject_label);
$("#feedback_form_wrapper label[data-field='details']").html(details_label);
if (userAuthenticated && finishedLoadingCourseOptions && courseOptions.length > 1) {
$('.js-course-id-anchor').html([
'<label for="feedback_form_course">' + '${_("Course") | n, js_escaped_string}' + '</label>',
'<select name="course_id" id="feedback_form_course" class="feedback-form-select">',
courseOptions.join(''),
'</select>'
].join(''));
}
$("#help_wrapper").css("display", "none");
$("#feedback_form_wrapper").css("display", "block");
$closeButton.focus();
},
loadCourseOptions = function() {
courseOptionsLoadInProgress = true;
$.ajax({
url: '/api/enrollment/v1/enrollment',
success: function(data) {
var i,
courseDetails,
courseName,
courseId,
option,
defaultOptionText = '${_("- Select -") | n, js_escaped_string}',
markedSelectedOption = false;
// Make sure courseOptions is empty before we begin pushing options into it.
courseOptions = [];
for (i = 0; i < data.length; i++) {
courseDetails = data[i].course_details;
if (!courseDetails) {
continue;
}
courseName = courseDetails.course_name;
courseId = courseDetails.course_id;
if (!(courseName && courseId)) {
continue;
}
// Build an option for this course and select it if it's the course we're currently viewing.
if (!markedSelectedOption && courseId === currentCourseId) {
option = buildCourseOption(courseName, courseId, true);
markedSelectedOption = true;
} else {
option = buildCourseOption(courseName, courseId, false);
}
courseOptions.push(option);
}
// Build the default option and select it if we haven't already selected another option.
option = buildCourseOption(defaultOptionText, '', !markedSelectedOption);
// Add the default option to the head of the courseOptions Array.
courseOptions.unshift(option);
finishedLoadingCourseOptions = true;
},
complete: function() {
courseOptionsLoadInProgress = false;
}
});
},
buildCourseOption = function(courseName, courseId, selected) {
var option = '<option value="' + _.escape(courseId) + '"';
if (selected) {
option += ' selected';
}
option += '>' + _.escape(courseName) + '</option>';
return option;
};
% if user.is_authenticated:
userAuthenticated = true;
% endif
% if course:
currentCourseId = "${unicode(course.id) | n, js_escaped_string}";
% endif
DialogTabControls.setKeydownListener($helpModal, $closeButton);
$(".help-tab").click(function() {
if (userAuthenticated && !finishedLoadingCourseOptions && !courseOptionsLoadInProgress) {
loadCourseOptions();
}
$helpModal.css("position", "absolute");
DialogTabControls.initializeTabKeyValues("#help_wrapper", $closeButton);
$(".field-error").removeClass("field-error");
$feedbackForm[0].reset();
$("#feedback_form input[type='submit']").removeAttr("disabled");
$("#feedback_form_wrapper").css("display", "none");
$("#feedback_error").css("display", "none");
$("#feedback_form_details_tip").css("display", "none");
$("#feedback_success_wrapper").css("display", "none");
$("#help_wrapper").css("display", "block");
$helpModal.attr("aria-hidden", "false");
$closeButton.click(onModalClose);
$leanOverlay.click(onModalClose);
$("button.close-modal").attr('tabindex', 0);
$closeButton.focus();
});
$("#feedback_link_problem").click(function(event) {
$("#feedback_form_details_tip").css({"display": "block", "padding-bottom": "5px"});
showFeedback(
event,
"${_('problem') | n, js_escaped_string}",
"${_('Report a Problem') | n, js_escaped_string}",
"${_('Brief description of the problem') + '*' | n, js_escaped_string}" ,
"${Text(_('Details of the problem you are encountering{asterisk}')).format(
asterisk='*',
) | n, js_escaped_string}"
);
});
$("#feedback_link_suggestion").click(function(event) {
showFeedback(
event,
"${_('suggestion') | n, js_escaped_string}",
"${_('Make a Suggestion') | n, js_escaped_string}",
"${_('Brief description of your suggestion') + '*' | n, js_escaped_string}",
"${_('Details') + '*' | n, js_escaped_string}"
);
});
$("#feedback_link_question").click(function(event) {
showFeedback(
event,
"${_('question') | n, js_escaped_string}",
"${_('Ask a Question') | n, js_escaped_string}",
"${_('Brief summary of your question') + '*' | n, js_escaped_string}",
"${_('Details') + '*' | n, js_escaped_string}"
);
});
$feedbackForm.submit(function() {
$("input[type='submit']", this).attr("disabled", "disabled");
$closeButton.focus();
});
$feedbackForm.on("ajax:complete", function() {
$("input[type='submit']", this).removeAttr("disabled");
});
$feedbackForm.on("ajax:success", function(event, data, status, xhr) {
$("#feedback_form_wrapper").css("display", "none");
$("#feedback_success_wrapper").css("display", "block");
DialogTabControls.initializeTabKeyValues("#feedback_success_wrapper", $closeButton);
$closeButton.focus();
});
$feedbackForm.on("ajax:error", function(event, xhr, status, error) {
$(".field-error").removeClass("field-error").removeAttr("aria-invalid");
var responseData;
try {
responseData = jQuery.parseJSON(xhr.responseText);
} catch(err) {
}
if (responseData) {
$("[data-field='"+responseData.field+"']").addClass("field-error").attr("aria-invalid", "true");
$("#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.') | n, js_escaped_string}";
% if settings.FEEDBACK_SUBMISSION_EMAIL:
htmlStr += " " + "${Text(_('Please {link_start}send us e-mail{link_end}.')).format(
link_start=HTML('<button type="button" id="feedback_email">'),
link_end=HTML('</button>'),
) | n, js_escaped_string}";
% 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.') | n, js_escaped_string}";
% 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 | n, js_escaped_string}" +
"?subject=" + $("#feedback_form input[name='subject']").val() +
"&body=" + $("#feedback_form textarea[name='details']").val();
window.open(mailto);
e.preventDefault();
});
%endif
}
// Make change explicit to assistive technology
$("#feedback_error").focus();
});
});
</script>
%endif

View File

@@ -99,7 +99,6 @@ from openedx.core.djangoapps.lang_pref.api import header_language_selector_is_en
<![endif]-->
% endif
<%include file="../help_modal.html"/>
% if settings.FEATURES.get('ENABLE_COOKIE_CONSENT', False):
<%include file="../widgets/cookie-consent.html" />
% endif
% endif

View File

@@ -7,15 +7,9 @@ import mimetypes
from django.conf import settings
from django.test import TestCase
from django.urls import reverse
from mock import patch
from six import text_type
from edxmako import LOOKUP, add_lookup
from microsite_configuration import microsite
from openedx.features.course_experience import course_home_url_name
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
log = logging.getLogger(__name__)
@@ -56,21 +50,3 @@ class TemplateLookupTests(TestCase):
microsite.enable_microsites(log)
directories = LOOKUP['main'].directories
self.assertEqual(len([directory for directory in directories if 'external_module' in directory]), 1)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_FEEDBACK_SUBMISSION': True})
class HelpModalTests(ModuleStoreTestCase):
"""Tests for the help modal"""
def setUp(self):
super(HelpModalTests, self).setUp()
self.course = CourseFactory.create()
def test_simple_test(self):
"""
Simple test to make sure that you don't get a 500 error when the modal
is enabled.
"""
url = reverse(course_home_url_name(self.course.id), args=[text_type(self.course.id)])
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)

View File

@@ -90,9 +90,6 @@ urlpatterns = [
url(r'^i18n/', include('django.conf.urls.i18n')),
# Feedback Form endpoint
url(r'^submit_feedback$', util_views.submit_feedback),
# Enrollment API RESTful endpoints
url(r'^api/enrollment/v1/', include('openedx.core.djangoapps.enrollments.urls')),