diff --git a/cms/envs/bok_choy.yml b/cms/envs/bok_choy.yml index 4388bfa1c0..8c44975e97 100644 --- a/cms/envs/bok_choy.yml +++ b/cms/envs/bok_choy.yml @@ -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", diff --git a/cms/envs/bok_choy_docker.env.json b/cms/envs/bok_choy_docker.env.json index d44346f75f..a1515c97f1 100644 --- a/cms/envs/bok_choy_docker.env.json +++ b/cms/envs/bok_choy_docker.env.json @@ -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", diff --git a/cms/envs/bok_choy_docker.yml b/cms/envs/bok_choy_docker.yml index 3a6b0ae27d..cd2da8f7f2 100644 --- a/cms/envs/bok_choy_docker.yml +++ b/cms/envs/bok_choy_docker.yml @@ -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", diff --git a/common/djangoapps/util/tests/test_submit_feedback.py b/common/djangoapps/util/tests/test_submit_feedback.py deleted file mode 100644 index abfe3bf3f3..0000000000 --- a/common/djangoapps/util/tests/test_submit_feedback.py +++ /dev/null @@ -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) diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index 2ea29614fe..1659250536 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -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 diff --git a/lms/envs/bok_choy.yml b/lms/envs/bok_choy.yml index 05ae4adb4a..f41199d4af 100644 --- a/lms/envs/bok_choy.yml +++ b/lms/envs/bok_choy.yml @@ -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", diff --git a/lms/envs/bok_choy_docker.yml b/lms/envs/bok_choy_docker.yml index be8b4eb3ec..4f136cef69 100644 --- a/lms/envs/bok_choy_docker.yml +++ b/lms/envs/bok_choy_docker.yml @@ -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", diff --git a/lms/envs/common.py b/lms/envs/common.py index c98317651d..49f46fe363 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -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 = '' diff --git a/lms/envs/production.py b/lms/envs/production.py index 5d0f102777..ec09dc0f06 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -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 diff --git a/lms/templates/help_modal.html b/lms/templates/help_modal.html deleted file mode 100644 index 13703d2a43..0000000000 --- a/lms/templates/help_modal.html +++ /dev/null @@ -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): - -
- - - - - -%endif diff --git a/lms/templates/navigation/navigation.html b/lms/templates/navigation/navigation.html index 2e399419a9..0e6c0b305c 100644 --- a/lms/templates/navigation/navigation.html +++ b/lms/templates/navigation/navigation.html @@ -99,7 +99,6 @@ from openedx.core.djangoapps.lang_pref.api import header_language_selector_is_en % endif -<%include file="../help_modal.html"/> % if settings.FEATURES.get('ENABLE_COOKIE_CONSENT', False): <%include file="../widgets/cookie-consent.html" /> -% endif +% endif \ No newline at end of file diff --git a/lms/tests.py b/lms/tests.py index 6a573e20e1..43b7e9f5d8 100644 --- a/lms/tests.py +++ b/lms/tests.py @@ -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) diff --git a/lms/urls.py b/lms/urls.py index 39d04ff99c..12d8e87922 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -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')),