diff --git a/lms/djangoapps/support/static/support/jsx/entitlements/index.jsx b/lms/djangoapps/support/static/support/jsx/entitlements/index.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ce5b7487ccb17b7d55d3583a0182028d2445b90e --- /dev/null +++ b/lms/djangoapps/support/static/support/jsx/entitlements/index.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +const EntitlementSupportPage = () => ( + <div> + Base Entitlement Support Page + </div> +); + +export default EntitlementSupportPage; diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index 32b8489ad951fb1dd6774593f219894d91231a22..9e853c908148907109b84b9b8fa9c23db37c2597 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -437,82 +437,3 @@ class SupportViewEnrollmentsTests(SharedModuleStoreTestCase, SupportViewTestCase ) verified_mode.expiration_datetime = datetime(year=1970, month=1, day=9, tzinfo=UTC) verified_mode.save() - - -@ddt.ddt -class SupportViewCourseEntitlementsTests(SupportViewTestCase): - """ Tests for the course entitlement support view.""" - - def setUp(self): - super(SupportViewCourseEntitlementsTests, self).setUp() - self.user = UserFactory(is_staff=True) - SupportStaffRole().add_users(self.user) - self.client.login(username=self.user.username, password=TEST_PASSWORD) - - self.student = UserFactory.create(username='student', email='test@example.com', password='test') - self.course_uuid = uuid4() - - self.url = reverse('support:course_entitlement') - - @ddt.data('username', 'email') - def test_get_entitlements(self, search_string_type): - CourseEntitlementFactory.create(mode=CourseMode.VERIFIED, user=self.student, course_uuid=self.course_uuid) - url = self.url + getattr(self.student, search_string_type) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - data = json.loads(response.content) - self.assertEqual(len(data), 1) - self.assertDictContainsSubset({ - 'user': self.student.username, - 'course_uuid': unicode(self.course_uuid), - 'enrollment_course_run': None, - 'mode': CourseMode.VERIFIED, - 'support_details': [] - }, data[0]) - - def test_reinstate_entitlement(self): - selected_run = CourseEnrollmentFactory(mode=CourseMode.VERIFIED, user=self.student) - expired_entitlement = CourseEntitlementFactory.create( - mode=CourseMode.VERIFIED, user=self.student, enrollment_course_run=selected_run, expired_at=datetime.now() - ) - url = self.url + self.student.username - response = self.client.put(url, data=json.dumps( - { - 'entitlement_uuid': unicode(expired_entitlement.uuid), - 'reason': CourseEntitlementSupportDetail.LEAVE_SESSION - }), - content_type='application/json' - ) - self.assertEqual(response.status_code, 200) - data = json.loads(response.content) - self.assertEqual(len(data['support_details']), 1) - self.assertDictContainsSubset({ - 'support_user': self.user.username, - 'reason': CourseEntitlementSupportDetail.LEAVE_SESSION, - 'comments': None, - 'unenrolled_run': unicode(selected_run.course_id) - }, data['support_details'][0]) - - def test_create_entitlement(self): - CourseEntitlementFactory.create( - mode=CourseMode.VERIFIED, user=self.student, course_uuid=self.course_uuid, expired_at=datetime.now() - ) - url = self.url + self.student.username - response = self.client.post( - url, - data=json.dumps({ - 'course_uuid': unicode(self.course_uuid), - 'reason': CourseEntitlementSupportDetail.LEARNER_REQUEST_NEW, - 'mode': CourseMode.VERIFIED - }), - content_type='application/json', - ) - self.assertEqual(response.status_code, 201) - data = json.loads(response.content) - self.assertEqual(len(data['support_details']), 1) - self.assertDictContainsSubset({ - 'support_user': self.user.username, - 'reason': CourseEntitlementSupportDetail.LEARNER_REQUEST_NEW, - 'comments': None, - 'unenrolled_run': None - }, data['support_details'][0]) diff --git a/lms/djangoapps/support/urls.py b/lms/djangoapps/support/urls.py index 1bd324d23a634303dc2e17b3fd8c681336f2efd8..494bfcb1a4c7dec27fcd7f9daa6076c033b3ba25 100644 --- a/lms/djangoapps/support/urls.py +++ b/lms/djangoapps/support/urls.py @@ -11,21 +11,13 @@ from support.views.index import index from support.views.manage_user import ManageUserDetailView, ManageUserSupportView from support.views.refund import RefundSupportView -COURSE_ENTITLEMENTS_VIEW = EntitlementSupportView.as_view({ - 'get': 'list', - 'post': 'create', - 'put': 'update' -}) +COURSE_ENTITLEMENTS_VIEW = EntitlementSupportView.as_view() urlpatterns = [ url(r'^$', index, name="index"), url(r'^certificates/?$', CertificatesSupportView.as_view(), name="certificates"), url(r'^refund/?$', RefundSupportView.as_view(), name="refund"), - url( - r'^course_entitlement/(?P<username_or_email>[\w.@+-]+)?$', - COURSE_ENTITLEMENTS_VIEW, - name="course_entitlement" - ), + url(r'^course_entitlement/?$', COURSE_ENTITLEMENTS_VIEW, name="course_entitlement"), url(r'^enrollment/?$', EnrollmentSupportView.as_view(), name="enrollment"), url(r'^contact_us/?$', ContactUsView.as_view(), name="contact_us"), url( diff --git a/lms/djangoapps/support/views/course_entitlements.py b/lms/djangoapps/support/views/course_entitlements.py index 79d64fdced237c4d784e50ff268f24e7f070c41b..6a93c964641f517a12fec0cc5a6549e694be5c98 100644 --- a/lms/djangoapps/support/views/course_entitlements.py +++ b/lms/djangoapps/support/views/course_entitlements.py @@ -6,20 +6,43 @@ from django.db import DatabaseError, transaction from django.db.models import Q from django.http import HttpResponseBadRequest from django.utils.decorators import method_decorator +from django.views.generic import View from edx_rest_framework_extensions.authentication import JwtAuthentication from rest_framework import permissions, status, viewsets from rest_framework.response import Response +from edxmako.shortcuts import render_to_response from entitlements.api.v1.permissions import IsAdminOrAuthenticatedReadOnly from entitlements.api.v1.serializers import SupportCourseEntitlementSerializer from entitlements.models import CourseEntitlement, CourseEntitlementSupportDetail +from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.support.decorators import require_support_permission from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf REQUIRED_CREATION_FIELDS = ['course_uuid', 'reason', 'mode'] -class EntitlementSupportView(viewsets.ModelViewSet): +class EntitlementSupportView(View): + """ + View for viewing and changing learner enrollments, used by the + support team. + """ + @method_decorator(require_support_permission) + def get(self, request): + """Render the enrollment support tool view.""" + support_actions = CourseEntitlementSupportDetail.get_support_actions_list() + + ecommerce_url = EcommerceService().get_order_dashboard_url() + context = { + 'username': request.GET.get('user', ''), + 'uses_bootstrap': True, + 'ecommerce_url': ecommerce_url, + 'support_actions': support_actions + } + return render_to_response('support/entitlement.html', context) + + +class EntitlementSupportListView(viewsets.ModelViewSet): """ Allows viewing and changing learner course entitlements, used the support team. """ diff --git a/lms/templates/support/entitlement.html b/lms/templates/support/entitlement.html new file mode 100644 index 0000000000000000000000000000000000000000..f193160f4c045f6162076b12e34a1c5f0209a3a6 --- /dev/null +++ b/lms/templates/support/entitlement.html @@ -0,0 +1,34 @@ +<%page expression_filter="h"/> + +<%! +from django.utils.translation import ugettext as _ +from openedx.core.djangolib.js_utils import js_escaped_string +%> + +## Override the default styles_version to use Bootstrap +<%! main_css = "css/bootstrap/lms-main.css" %> + +<%namespace name='static' file='../static_content.html'/> + +<%inherit file="../main.html" /> + +<%block name="js_extra"> +</%block> + +<%block name="pagetitle"> + ${_("Entitlements")} +</%block> + +<%block name="content"> + <section class="container outside-app"> + ${static.renderReact( + component="EntitlementSupportPage", + id="entitlement-support-page", + props={ + 'ecommerceUrl': ecommerce_url, + 'supportReasons': support_actions + } + ) + } + </section> +</%block> diff --git a/package-lock.json b/package-lock.json index 06fd3ea6672ce52918547c33817a14b39da0f698..43d52557cc6c4255fd1e73d5bcfda4ae27b6b106 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7981,11 +7981,10 @@ "integrity": "sha1-eHTi04kR0nGeoncx0yRF2l7EOUw=" }, "react": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", - "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.1.0.tgz", + "integrity": "sha512-hvKYlKqde2JNnNiEzORvSA0J1L7uSZ43l+J89ZNoP4EXxQrVNH0CFj8vorfPou3w+1ou1BNMBir2VVsuXtETRA==", "requires": { - "create-react-class": "15.6.3", "fbjs": "0.8.16", "loose-envify": "1.3.1", "object-assign": "4.1.1", @@ -8003,9 +8002,9 @@ } }, "react-dom": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", - "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.0.tgz", + "integrity": "sha512-i9in5qW3H2PDinUPD9bnQK7tLAD8LhjYQ+fXi3nJOvVnxOO3ErHq6RNEnKY7pbjTPt155e74q7al8eBUuyLtew==", "requires": { "fbjs": "0.8.16", "loose-envify": "1.3.1", diff --git a/package.json b/package.json index c6d2a49b730a9e6f6e3fd6bd246c1df5e50ca765..9030300d23bd691000f52e3725b974d27ece8a80 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,8 @@ "popper.js": "1.12.9", "prop-types": "15.6.0", "raw-loader": "0.5.1", - "react": "15.6.2", - "react-dom": "15.6.2", + "react": "16.1.0", + "react-dom": "16.1.0", "react-slick": "0.16.0", "requirejs": "2.3.5", "rtlcss": "2.2.1", diff --git a/webpack.common.config.js b/webpack.common.config.js index 31c0cc1bd8372db40f683d8a3fe81dca08345cc5..2c71829b2cffa2adaa24455638f024160f29d2ff 100644 --- a/webpack.common.config.js +++ b/webpack.common.config.js @@ -29,6 +29,7 @@ module.exports = { LearnerAnalyticsDashboard: './lms/static/js/learner_analytics_dashboard/LearnerAnalyticsDashboard.jsx', UpsellExperimentModal: './lms/static/common/js/components/UpsellExperimentModal.jsx', PortfolioExperimentUpsellModal: './lms/static/common/js/components/PortfolioExperimentUpsellModal.jsx', + EntitlementSupportPage: './lms/djangoapps/support/static/support/jsx/entitlements/index.jsx', // Learner Dashboard EntitlementFactory: './lms/static/js/learner_dashboard/course_entitlement_factory.js',