diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 0cbc6436801815b5732ae6efcc21db0f69686818..b5b34af7af6ce5c3e5a680fdf7ab3420c1fb8d07 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -19,6 +19,7 @@ from mock import patch from opaque_keys import InvalidKeyError from pyquery import PyQuery as pq +from bulk_email.models import BulkEmailFlag from entitlements.tests.factories import CourseEntitlementFactory from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -238,6 +239,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): Tests for the student dashboard. """ + EMAIL_SETTINGS_ELEMENT_ID = "#actions-item-email-settings-0" ENABLED_SIGNALS = ['course_published'] TOMORROW = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) THREE_YEARS_AGO = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=(365 * 3)) @@ -533,6 +535,33 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): self.assertIn('You can no longer change sessions.', response.content) self.assertIn('Related Programs:', response.content) + @patch.object(CourseOverview, 'get_from_id') + @patch.object(BulkEmailFlag, 'feature_enabled') + def test_email_settings_fulfilled_entitlement(self, mock_email_feature, mock_course_overview): + """ + Assert that the Email Settings action is shown when the user has a fulfilled entitlement. + """ + mock_email_feature.return_value = True + mock_course_overview.return_value = CourseOverviewFactory( + start=self.TOMORROW, self_paced=True, enrollment_end=self.TOMORROW + ) + course_enrollment = CourseEnrollmentFactory(user=self.user) + CourseEntitlementFactory(user=self.user, enrollment_course_run=course_enrollment) + response = self.client.get(self.path) + self.assertEqual(pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length, 1) + + @patch.object(CourseOverview, 'get_from_id') + @patch.object(BulkEmailFlag, 'feature_enabled') + def test_email_settings_unfulfilled_entitlement(self, mock_email_feature, mock_course_overview): + """ + Assert that the Email Settings action is not shown when the entitlement is not fulfilled. + """ + mock_email_feature.return_value = True + mock_course_overview.return_value = CourseOverviewFactory(start=self.TOMORROW) + CourseEntitlementFactory(user=self.user) + response = self.client.get(self.path) + self.assertEqual(pq(response.content)(self.EMAIL_SETTINGS_ELEMENT_ID).length, 0) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @override_settings(BRANCH_IO_KEY='test_key') diff --git a/lms/static/js/dashboard/legacy.js b/lms/static/js/dashboard/legacy.js index be8df1fc5463b41416a1a210569a92e1732e1017..ffe87b83183661c5e500f71aa105f3ea899ca46d 100644 --- a/lms/static/js/dashboard/legacy.js +++ b/lms/static/js/dashboard/legacy.js @@ -131,7 +131,6 @@ if ($(event.target).data('optout') === 'False') { $('#receive_emails').prop('checked', true); } - edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event); }); $('.action-unenroll').click(function(event) { var isPaidCourse = $(event.target).data('course-is-paid-course') === 'True'; @@ -211,6 +210,7 @@ }); $('.action-email-settings').each(function(index) { + $(this).attr('id', 'email-settings-' + index); // a bit of a hack, but gets the unique selector for the modal trigger var trigger = '#' + $(this).attr('id'); accessibleModal( @@ -219,7 +219,6 @@ '#email-settings-modal', '#dashboard-main' ); - $(this).attr('id', 'email-settings-' + index); }); $('.action-unenroll').each(function(index) { diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 820733a5f546bd5e1419b7b31188b83de8587d52..c0e36410eb6aa5a2e3fc731ba20b9e6f71c4fb62 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -167,14 +167,17 @@ from student.models import CourseEnrollment if not next_session: continue enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type']) - + # We only show email settings for entitlement cards if the entitlement has an associated enrollment + show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for) + else: + show_email_settings = (enrollment.course_id in show_email_settings_for) + session_id = enrollment.course_id show_courseware_link = (session_id in show_courseware_links_for) cert_status = cert_statuses.get(session_id) can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable() can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False credit_status = credit_statuses.get(session_id) - show_email_settings = (session_id in show_email_settings_for) course_mode_info = all_course_modes.get(session_id) is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid) is_course_blocked = (session_id in block_courses) diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 83b211ea1582d0a61aef8587b766dff6a65f0248..3a653e586883629a2f151e2c37e6e3fec0d55a03 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -235,11 +235,11 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_ % endif % endif - ## Right now, the gear dropdown for entitlements only contains the 'unenroll' link, so we should hide the - ## gear altogether if the user is unable to unenroll/refund their entitlement. Later, when we add more options - ## to the gear dropdown, we can remove this check. - % if entitlement and can_refund_entitlement: - <%include file='_dashboard_entitlement_actions.html' args='course_overview=course_overview,entitlement=entitlement,dashboard_index=dashboard_index, can_refund_entitlement=can_refund_entitlement'/> + ## We should only show the gear dropdown if the user is able to refund/unenroll from their entitlement + ## and/or if they have selected a course run and email_settings are enabled + ## as these are the only actions currently available + % if entitlement and (can_refund_entitlement or show_email_settings): + <%include file='_dashboard_entitlement_actions.html' args='course_overview=course_overview,entitlement=entitlement,dashboard_index=dashboard_index, can_refund_entitlement=can_refund_entitlement, show_email_settings=show_email_settings'/> % elif not entitlement: <div class="wrapper-action-more" data-course-key="${enrollment.course_id}"> <button type="button" class="action action-more" id="actions-dropdown-link-${dashboard_index}" aria-haspopup="true" aria-expanded="false" aria-controls="actions-dropdown-${dashboard_index}" data-course-number="${course_overview.number}" data-course-name="${course_overview.display_name_with_default}" data-dashboard-index="${dashboard_index}"> diff --git a/lms/templates/dashboard/_dashboard_entitlement_actions.html b/lms/templates/dashboard/_dashboard_entitlement_actions.html index 08455f8d9957ed2394508c2d586151f0484a3dab..536dbbcff8330df70c0cc6180cf208d515f53ee3 100644 --- a/lms/templates/dashboard/_dashboard_entitlement_actions.html +++ b/lms/templates/dashboard/_dashboard_entitlement_actions.html @@ -1,4 +1,4 @@ -<%page args="course_overview, entitlement, dashboard_index, can_refund_entitlement" expression_filter="h"/> +<%page args="course_overview, entitlement, dashboard_index, can_refund_entitlement, show_email_settings" expression_filter="h"/> <%! from django.utils.translation import ugettext as _ @@ -43,6 +43,19 @@ dropdown_btn_id = "entitlement-actions-dropdown-btn-{}".format(dashboard_index) </a> </li> % endif - </ul> + % if show_email_settings: + <li class="entitlement-actions-item" id="actions-item-email-settings-${dashboard_index}" role="menuitem"> + ## href and rel must be defined for compatibility with lms/static/js/leanModal.js + ## data-dropdown-selector and data-dropdown-button-selector must be defined for compatibility with lms/static/js/dashboard/dropdown.js + <a href="#email-settings-modal" class="entitlement-action action-email-settings" rel="leanModal" + data-dropdown-selector="#${dropdown_id}" + data-dropdown-button-selector="#${dropdown_btn_id}" + data-course-id="${course_overview.id}" + data-course-number="${course_overview.number}" + data-dashboard-index="${dashboard_index}" + data-optout="${unicode(course_overview.id) in course_optouts}">${_('Email Settings')}</a> + </li> + % endif + </ul> </div> </div> diff --git a/themes/edx.org/lms/templates/dashboard.html b/themes/edx.org/lms/templates/dashboard.html index 577d69e42e4a4ea719d44a041d112eb4158ef368..1da55c5130e6fe226f736e9f0f58f0708f2f719c 100644 --- a/themes/edx.org/lms/templates/dashboard.html +++ b/themes/edx.org/lms/templates/dashboard.html @@ -163,6 +163,10 @@ from student.models import CourseEnrollment if not next_session: continue enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type']) + # We only show email settings for entitlement cards if the entitlement has an associated enrollment + show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for) + else: + show_email_settings = (enrollment.course_id in show_email_settings_for) session_id = enrollment.course_id show_courseware_link = (session_id in show_courseware_links_for) @@ -170,7 +174,6 @@ from student.models import CourseEnrollment can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable() can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False credit_status = credit_statuses.get(session_id) - show_email_settings = (session_id in show_email_settings_for) course_mode_info = all_course_modes.get(session_id) is_paid_course = True if entitlement else (session_id in enrolled_courses_either_paid) is_course_blocked = (session_id in block_courses)