diff --git a/cms/envs/common.py b/cms/envs/common.py index 45a8e97f3e51..8daa08aeb1c8 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1449,9 +1449,8 @@ 'edx-ui-toolkit/js/utils/string-utils.js', 'edx-ui-toolkit/js/utils/html-utils.js', - # Load Bootstrap and supporting libraries - 'common/js/vendor/popper.js', - 'common/js/vendor/bootstrap.js', + # Here we were loading Bootstrap and supporting libraries, but it no longer seems to be needed for any Studio UI. + # 'common/js/vendor/bootstrap.bundle.js', # Finally load RequireJS 'common/js/vendor/require.js' diff --git a/common/djangoapps/entitlements/rest_api/v1/permissions.py b/common/djangoapps/entitlements/rest_api/v1/permissions.py index 6a705d9feed5..db14f05049c3 100644 --- a/common/djangoapps/entitlements/rest_api/v1/permissions.py +++ b/common/djangoapps/entitlements/rest_api/v1/permissions.py @@ -4,7 +4,6 @@ """ -from django.conf import settings from rest_framework.permissions import SAFE_METHODS, BasePermission from lms.djangoapps.courseware.access import has_access @@ -22,12 +21,3 @@ def has_permission(self, request, view): return request.user.is_authenticated else: return request.user.is_staff or has_access(request.user, "support", "global") - - -class IsSubscriptionWorkerUser(BasePermission): - """ - Method that will require the request to be coming from the subscriptions service worker user. - """ - - def has_permission(self, request, view): - return request.user.username == settings.SUBSCRIPTIONS_SERVICE_WORKER_USERNAME diff --git a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py index 34abc39c0096..86d4ae6a87e1 100644 --- a/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py @@ -6,7 +6,6 @@ import uuid from datetime import datetime, timedelta from unittest.mock import patch -from uuid import uuid4 from django.conf import settings from django.urls import reverse @@ -1236,160 +1235,3 @@ def test_user_is_not_unenrolled_on_failed_refund( assert CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is not None assert course_entitlement.expired_at is None - - -@skip_unless_lms -class RevokeSubscriptionsVerifiedAccessViewTest(ModuleStoreTestCase): - """ - Tests for the RevokeVerifiedAccessView - """ - REVOKE_VERIFIED_ACCESS_PATH = 'entitlements_api:v1:revoke_subscriptions_verified_access' - - def setUp(self): - super().setUp() - self.user = UserFactory(username="subscriptions_worker", is_staff=True) - self.client.login(username=self.user.username, password=TEST_PASSWORD) - self.course = CourseFactory() - self.course_mode1 = CourseModeFactory( - course_id=self.course.id, # pylint: disable=no-member - mode_slug=CourseMode.VERIFIED, - expiration_datetime=now() + timedelta(days=1) - ) - self.course_mode2 = CourseModeFactory( - course_id=self.course.id, # pylint: disable=no-member - mode_slug=CourseMode.AUDIT, - expiration_datetime=now() + timedelta(days=1) - ) - - @patch('common.djangoapps.entitlements.rest_api.v1.views.get_courses_completion_status') - def test_revoke_access_success(self, mock_get_courses_completion_status): - mock_get_courses_completion_status.return_value = ([], False) - enrollment = CourseEnrollmentFactory.create( - user=self.user, - course_id=self.course.id, # pylint: disable=no-member - is_active=True, - mode=CourseMode.VERIFIED - ) - course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) - url = reverse(self.REVOKE_VERIFIED_ACCESS_PATH) - - assert course_entitlement.enrollment_course_run is not None - - response = self.client.post( - url, - data={ - "entitlement_uuids": [str(course_entitlement.uuid)], - "lms_user_id": self.user.id - }, - content_type='application/json', - ) - assert response.status_code == 204 - - course_entitlement.refresh_from_db() - enrollment.refresh_from_db() - assert course_entitlement.expired_at is not None - assert course_entitlement.enrollment_course_run is None - assert enrollment.mode == CourseMode.AUDIT - - @patch('common.djangoapps.entitlements.rest_api.v1.views.get_courses_completion_status') - def test_already_completed_course(self, mock_get_courses_completion_status): - enrollment = CourseEnrollmentFactory.create( - user=self.user, - course_id=self.course.id, # pylint: disable=no-member - is_active=True, - mode=CourseMode.VERIFIED - ) - mock_get_courses_completion_status.return_value = ([str(enrollment.course_id)], False) - course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) - url = reverse(self.REVOKE_VERIFIED_ACCESS_PATH) - - assert course_entitlement.enrollment_course_run is not None - - response = self.client.post( - url, - data={ - "entitlement_uuids": [str(course_entitlement.uuid)], - "lms_user_id": self.user.id - }, - content_type='application/json', - ) - assert response.status_code == 204 - - course_entitlement.refresh_from_db() - assert course_entitlement.expired_at is None - assert course_entitlement.enrollment_course_run.mode == CourseMode.VERIFIED - - @patch('common.djangoapps.entitlements.rest_api.v1.views.log.info') - def test_revoke_access_invalid_uuid(self, mock_log): - url = reverse(self.REVOKE_VERIFIED_ACCESS_PATH) - entitlement_uuids = [str(uuid4())] - response = self.client.post( - url, - data={ - "entitlement_uuids": entitlement_uuids, - "lms_user_id": self.user.id - }, - content_type='application/json', - ) - - mock_log.assert_called_once_with("B2C_SUBSCRIPTIONS: Entitlements not found for the provided" - " entitlements data: %s and user: %s", - entitlement_uuids, - self.user.id) - assert response.status_code == 204 - - def test_revoke_access_unauthorized_user(self): - user = UserFactory(is_staff=True, username='not_subscriptions_worker') - self.client.login(username=user.username, password=TEST_PASSWORD) - - enrollment = CourseEnrollmentFactory.create( - user=self.user, - course_id=self.course.id, # pylint: disable=no-member - is_active=True, - mode=CourseMode.VERIFIED - ) - course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) - url = reverse(self.REVOKE_VERIFIED_ACCESS_PATH) - - assert course_entitlement.enrollment_course_run is not None - - response = self.client.post( - url, - data={ - "entitlement_uuids": [], - "lms_user_id": self.user.id - }, - content_type='application/json', - ) - assert response.status_code == 403 - - course_entitlement.refresh_from_db() - assert course_entitlement.expired_at is None - assert course_entitlement.enrollment_course_run.mode == CourseMode.VERIFIED - - @patch('common.djangoapps.entitlements.tasks.retry_revoke_subscriptions_verified_access.apply_async') - @patch('common.djangoapps.entitlements.rest_api.v1.views.get_courses_completion_status') - def test_course_completion_exception_triggers_task(self, mock_get_courses_completion_status, mock_task): - mock_get_courses_completion_status.return_value = ([], True) - enrollment = CourseEnrollmentFactory.create( - user=self.user, - course_id=self.course.id, # pylint: disable=no-member - is_active=True, - mode=CourseMode.VERIFIED - ) - course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) - - url = reverse(self.REVOKE_VERIFIED_ACCESS_PATH) - - response = self.client.post( - url, - data={ - "entitlement_uuids": [str(course_entitlement.uuid)], - "lms_user_id": self.user.id - }, - content_type='application/json', - ) - assert response.status_code == 204 - mock_task.assert_called_once_with(args=([str(course_entitlement.uuid)], - [str(enrollment.course_id)], - self.user.username)) diff --git a/common/djangoapps/entitlements/rest_api/v1/throttles.py b/common/djangoapps/entitlements/rest_api/v1/throttles.py deleted file mode 100644 index 3a010c76afe7..000000000000 --- a/common/djangoapps/entitlements/rest_api/v1/throttles.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Throttle classes for the entitlements API. -""" - -from django.conf import settings -from rest_framework.throttling import UserRateThrottle - - -class ServiceUserThrottle(UserRateThrottle): - """A throttle allowing service users to override rate limiting""" - - def allow_request(self, request, view): - """Returns True if the request is coming from one of the service users - and defaults to UserRateThrottle's configured setting otherwise. - """ - service_users = [ - settings.SUBSCRIPTIONS_SERVICE_WORKER_USERNAME - ] - if request.user.username in service_users: - return True - return super().allow_request(request, view) diff --git a/common/djangoapps/entitlements/rest_api/v1/urls.py b/common/djangoapps/entitlements/rest_api/v1/urls.py index e1d98a2485c3..e04341b5ef50 100644 --- a/common/djangoapps/entitlements/rest_api/v1/urls.py +++ b/common/djangoapps/entitlements/rest_api/v1/urls.py @@ -6,7 +6,7 @@ from django.urls import path, re_path from rest_framework.routers import DefaultRouter -from .views import EntitlementEnrollmentViewSet, EntitlementViewSet, SubscriptionsRevokeVerifiedAccessView +from .views import EntitlementEnrollmentViewSet, EntitlementViewSet router = DefaultRouter() router.register(r'entitlements', EntitlementViewSet, basename='entitlements') @@ -24,9 +24,4 @@ ENROLLMENTS_VIEW, name='enrollments' ), - path( - 'subscriptions/entitlements/revoke', - SubscriptionsRevokeVerifiedAccessView.as_view(), - name='revoke_subscriptions_verified_access' - ) ] diff --git a/common/djangoapps/entitlements/rest_api/v1/views.py b/common/djangoapps/entitlements/rest_api/v1/views.py index 3306604d5d13..4f3dd54b52a7 100644 --- a/common/djangoapps/entitlements/rest_api/v1/views.py +++ b/common/djangoapps/entitlements/rest_api/v1/views.py @@ -15,7 +15,6 @@ from opaque_keys.edx.keys import CourseKey from rest_framework import permissions, status, viewsets from rest_framework.response import Response -from rest_framework.views import APIView from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.entitlements.models import ( # lint-amnesty, pylint: disable=line-too-long @@ -24,22 +23,13 @@ CourseEntitlementSupportDetail ) from common.djangoapps.entitlements.rest_api.v1.filters import CourseEntitlementFilter -from common.djangoapps.entitlements.rest_api.v1.permissions import ( - IsAdminOrSupportOrAuthenticatedReadOnly, - IsSubscriptionWorkerUser -) +from common.djangoapps.entitlements.rest_api.v1.permissions import IsAdminOrSupportOrAuthenticatedReadOnly from common.djangoapps.entitlements.rest_api.v1.serializers import CourseEntitlementSerializer -from common.djangoapps.entitlements.rest_api.v1.throttles import ServiceUserThrottle -from common.djangoapps.entitlements.tasks import retry_revoke_subscriptions_verified_access -from common.djangoapps.entitlements.utils import ( - is_course_run_entitlement_fulfillable, - revoke_entitlements_and_downgrade_courses_to_audit -) +from common.djangoapps.entitlements.utils import is_course_run_entitlement_fulfillable from common.djangoapps.student.models import AlreadyEnrolledError, CourseEnrollment, CourseEnrollmentException from openedx.core.djangoapps.catalog.utils import get_course_runs_for_course, get_owners_for_course from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf -from openedx.core.djangoapps.credentials.utils import get_courses_completion_status from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in User = get_user_model() @@ -132,7 +122,6 @@ class EntitlementViewSet(viewsets.ModelViewSet): filter_backends = (DjangoFilterBackend,) filterset_class = CourseEntitlementFilter pagination_class = EntitlementsPagination - throttle_classes = (ServiceUserThrottle,) def get_queryset(self): user = self.request.user @@ -530,68 +519,3 @@ def destroy(self, request, uuid): }) return Response(status=status.HTTP_204_NO_CONTENT) - - -class SubscriptionsRevokeVerifiedAccessView(APIView): - """ - Endpoint for expiring entitlements for a user and downgrading the enrollments - to Audit mode. This endpoint accepts a list of entitlement UUIDs and will expire - the entitlements along with downgrading the related enrollments to Audit mode. - Only those enrollments are downgraded to Audit for which user has not been awarded - a completion certificate yet. - """ - authentication_classes = (JwtAuthentication, SessionAuthenticationCrossDomainCsrf,) - permission_classes = (permissions.IsAuthenticated, IsSubscriptionWorkerUser,) - throttle_classes = (ServiceUserThrottle,) - - def _process_revoke_and_downgrade_to_audit(self, course_entitlements, user_id, revocable_entitlement_uuids): - """ - Gets course completion status for the provided course entitlements and triggers the - revoke and downgrade to audit process for the course entitlements which are not completed. - Triggers the retry task asynchronously if there is an exception while getting the - course completion status. - """ - entitled_course_ids = [] - user = User.objects.get(id=user_id) - username = user.username - for course_entitlement in course_entitlements: - if course_entitlement.enrollment_course_run is not None: - entitled_course_ids.append(str(course_entitlement.enrollment_course_run.course_id)) - - log.info('B2C_SUBSCRIPTIONS: Getting course completion status for user [%s] and entitled_course_ids %s', - username, - entitled_course_ids) - awarded_cert_course_ids, is_exception = get_courses_completion_status(username, entitled_course_ids) - - if is_exception: - # Trigger the retry task asynchronously - log.exception('B2C_SUBSCRIPTIONS: Exception occurred while getting course completion status for user %s ' - 'and entitled_course_ids %s', - username, - entitled_course_ids) - retry_revoke_subscriptions_verified_access.apply_async(args=(revocable_entitlement_uuids, - entitled_course_ids, - username)) - return - revoke_entitlements_and_downgrade_courses_to_audit(course_entitlements, username, awarded_cert_course_ids, - revocable_entitlement_uuids) - - def post(self, request): - """ - Invokes the entitlements expiration process for the provided uuids and downgrades the - enrollments to Audit mode. - """ - revocable_entitlement_uuids = request.data.get('entitlement_uuids', []) - user_id = request.data.get('lms_user_id', None) - course_entitlements = (CourseEntitlement.objects.filter(uuid__in=revocable_entitlement_uuids). - select_related('user'). - select_related('enrollment_course_run')) - - if course_entitlements.exists(): - self._process_revoke_and_downgrade_to_audit(course_entitlements, user_id, revocable_entitlement_uuids) - return Response(status=status.HTTP_204_NO_CONTENT) - else: - log.info('B2C_SUBSCRIPTIONS: Entitlements not found for the provided entitlements data: %s and user: %s', - revocable_entitlement_uuids, - user_id) - return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/common/djangoapps/entitlements/tasks.py b/common/djangoapps/entitlements/tasks.py index 981879e21793..9bd200bc9056 100644 --- a/common/djangoapps/entitlements/tasks.py +++ b/common/djangoapps/entitlements/tasks.py @@ -4,15 +4,12 @@ import logging from celery import shared_task -from celery.exceptions import MaxRetriesExceededError from celery.utils.log import get_task_logger from django.conf import settings # lint-amnesty, pylint: disable=unused-import from django.contrib.auth import get_user_model from edx_django_utils.monitoring import set_code_owner_attribute from common.djangoapps.entitlements.models import CourseEntitlement, CourseEntitlementSupportDetail -from common.djangoapps.entitlements.utils import revoke_entitlements_and_downgrade_courses_to_audit -from openedx.core.djangoapps.credentials.utils import get_courses_completion_status LOGGER = get_task_logger(__name__) log = logging.getLogger(__name__) @@ -154,40 +151,3 @@ def expire_and_create_entitlements(self, entitlement_ids, support_username): '%d entries, task id :%s', len(entitlement_ids), self.request.id) - - -@shared_task(bind=True) -@set_code_owner_attribute -def retry_revoke_subscriptions_verified_access(self, revocable_entitlement_uuids, entitled_course_ids, username): - """ - Task to process course access revoke and move to audit. - This is called only if call to get_courses_completion_status fails due to any exception. - """ - LOGGER.info("B2C_SUBSCRIPTIONS: Running retry_revoke_subscriptions_verified_access for user [%s]," - " entitlement_uuids %s and entitled_course_ids %s", - username, - revocable_entitlement_uuids, - entitled_course_ids) - course_entitlements = CourseEntitlement.objects.filter(uuid__in=revocable_entitlement_uuids) - course_entitlements = course_entitlements.select_related('user').select_related('enrollment_course_run') - if course_entitlements.exists(): - awarded_cert_course_ids, is_exception = get_courses_completion_status(username, entitled_course_ids) - if is_exception: - try: - countdown = 2 ** self.request.retries - self.retry(countdown=countdown, max_retries=3) - except MaxRetriesExceededError: - LOGGER.exception( - 'B2C_SUBSCRIPTIONS: Failed to process retry_revoke_subscriptions_verified_access ' - 'for user [%s] and entitlement_uuids %s', - username, - revocable_entitlement_uuids - ) - return - revoke_entitlements_and_downgrade_courses_to_audit(course_entitlements, username, awarded_cert_course_ids, - revocable_entitlement_uuids) - else: - LOGGER.info('B2C_SUBSCRIPTIONS: Entitlements not found for the provided entitlements uuids %s ' - 'for user [%s] duing the retry_revoke_subscriptions_verified_access task', - revocable_entitlement_uuids, - username) diff --git a/common/djangoapps/util/tests/test_db.py b/common/djangoapps/util/tests/test_db.py index 4a16c2a20aa6..88358ce20512 100644 --- a/common/djangoapps/util/tests/test_db.py +++ b/common/djangoapps/util/tests/test_db.py @@ -1,5 +1,6 @@ """Tests for util.db module.""" +import unittest from io import StringIO import ddt @@ -120,6 +121,9 @@ class MigrationTests(TestCase): Tests for migrations. """ + @unittest.skip( + "Temporary skip for ENT-8971 while the client id and secret columns in Canvas replaced." + ) @override_settings(MIGRATION_MODULES={}) def test_migrations_are_in_sync(self): """ diff --git a/docs/hooks/events.rst b/docs/hooks/events.rst index 2ee070b8a276..bccb98e56a42 100644 --- a/docs/hooks/events.rst +++ b/docs/hooks/events.rst @@ -233,27 +233,27 @@ Content Authoring Events - 2023-07-20 * - `LIBRARY_BLOCK_CREATED `_ - - org.openedx.content_authoring.content_library.created.v1 + - org.openedx.content_authoring.library_block.created.v1 - 2023-07-20 * - `LIBRARY_BLOCK_UPDATED `_ - - org.openedx.content_authoring.content_library.updated.v1 + - org.openedx.content_authoring.library_block.updated.v1 - 2023-07-20 * - `LIBRARY_BLOCK_DELETED `_ - - org.openedx.content_authoring.content_library.deleted.v1 + - org.openedx.content_authoring.library_block.deleted.v1 - 2023-07-20 * - `LIBRARY_COLLECTION_CREATED `_ - - org.openedx.content_authoring.content.library.collection.created.v1 + - org.openedx.content_authoring.content_library.collection.created.v1 - 2024-08-23 * - `LIBRARY_COLLECTION_UPDATED `_ - - org.openedx.content_authoring.content.library.collection.updated.v1 + - org.openedx.content_authoring.content_library.collection.updated.v1 - 2024-08-23 * - `LIBRARY_COLLECTION_DELETED `_ - - org.openedx.content_authoring.content.library.collection.deleted.v1 + - org.openedx.content_authoring.content_library.collection.deleted.v1 - 2024-08-23 * - `CONTENT_OBJECT_ASSOCIATIONS_CHANGED `_ diff --git a/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 38c52e737e19..5e9afcc6d370 100644 --- a/docs/lms-openapi.yaml +++ b/docs/lms-openapi.yaml @@ -3461,29 +3461,6 @@ paths: in: path required: true type: string - /demographics/v1/demographics/status/: - get: - operationId: demographics_v1_demographics_status_list - summary: GET /api/user/v1/accounts/demographics/status - description: This is a Web API to determine the status of demographics related - features - parameters: [] - responses: - '200': - description: '' - tags: - - demographics - patch: - operationId: demographics_v1_demographics_status_partial_update - summary: PATCH /api/user/v1/accounts/demographics/status - description: This is a Web API to update fields that are dependent on user interaction. - parameters: [] - responses: - '200': - description: '' - tags: - - demographics - parameters: [] /discounts/course/{course_key_string}: get: operationId: discounts_course_read @@ -5300,19 +5277,6 @@ paths: required: true type: string format: uuid - /entitlements/v1/subscriptions/entitlements/revoke: - post: - operationId: entitlements_v1_subscriptions_entitlements_revoke_create - description: |- - Invokes the entitlements expiration process for the provided uuids and downgrades the - enrollments to Audit mode. - parameters: [] - responses: - '201': - description: '' - tags: - - entitlements - parameters: [] /experiments/v0/custom/REV-934/: get: operationId: experiments_v0_custom_REV-934_list @@ -6649,6 +6613,11 @@ paths: course, chapter, sequential, vertical, html, problem, video, and discussion. display_name: (str) The display name of the block. + course_progress: (dict) Contains information about how many assignments are in the course + and how many assignments the student has completed. + Included here: + * total_assignments_count: (int) Total course's assignments count. + * assignments_completed: (int) Assignments witch the student has completed. **Returns** @@ -6696,6 +6665,26 @@ paths: in: path required: true type: string + /mobile/{api_version}/course_info/{course_id}/enrollment_details: + get: + operationId: mobile_course_info_enrollment_details_list + summary: Handle the GET request + description: Returns user enrollment and course details. + parameters: [] + responses: + '200': + description: '' + tags: + - mobile + parameters: + - name: api_version + in: path + required: true + type: string + - name: course_id + in: path + required: true + type: string /mobile/{api_version}/course_info/{course_id}/handouts: get: operationId: mobile_course_info_handouts_list @@ -6861,6 +6850,10 @@ paths: An additional attribute "expiration" has been added to the response, which lists the date when access to the course will expire or null if it doesn't expire. + In v4 we added to the response primary object. Primary object contains the latest user's enrollment + or course where user has the latest progress. Primary object has been cut from user's + enrolments array and inserted into separated section with key `primary`. + **Example Request** GET /api/mobile/v1/users/{username}/course_enrollments/ @@ -6910,14 +6903,14 @@ paths: * mode: The type of certificate registration for this course (honor or certified). * url: URL to the downloadable version of the certificate, if exists. + * course_progress: Contains information about how many assignments are in the course + and how many assignments the student has completed. + * total_assignments_count: Total course's assignments count. + * assignments_completed: Assignments witch the student has completed. parameters: [] responses: '200': description: '' - schema: - type: array - items: - $ref: '#/definitions/CourseEnrollment' tags: - mobile parameters: @@ -7031,22 +7024,6 @@ paths: tags: - notifications parameters: [] - /notifications/channel/configurations/{course_key_string}: - patch: - operationId: notifications_channel_configurations_partial_update - description: Update an existing user notification preference for an entire channel - with the data in the request body. - parameters: [] - responses: - '200': - description: '' - tags: - - notifications - parameters: - - name: course_key_string - in: path - required: true - type: string /notifications/configurations/{course_key_string}: get: operationId: notifications_configurations_read @@ -7222,6 +7199,38 @@ paths: in: path required: true type: string + /notifications/preferences/update/{username}/{patch}/: + get: + operationId: notifications_preferences_update_read + description: |- + View to update user preferences from encrypted username and patch. + username and patch must be string + parameters: [] + responses: + '200': + description: '' + tags: + - notifications + post: + operationId: notifications_preferences_update_create + description: |- + View to update user preferences from encrypted username and patch. + username and patch must be string + parameters: [] + responses: + '201': + description: '' + tags: + - notifications + parameters: + - name: username + in: path + required: true + type: string + - name: patch + in: path + required: true + type: string /notifications/read/: patch: operationId: notifications_read_partial_update @@ -11731,39 +11740,6 @@ definitions: title: Course enrollments type: string readOnly: true - CourseEnrollment: - type: object - properties: - audit_access_expires: - title: Audit access expires - type: string - readOnly: true - created: - title: Created - type: string - format: date-time - readOnly: true - x-nullable: true - mode: - title: Mode - type: string - maxLength: 100 - minLength: 1 - is_active: - title: Is active - type: boolean - course: - title: Course - type: string - readOnly: true - certificate: - title: Certificate - type: string - readOnly: true - course_modes: - title: Course modes - type: string - readOnly: true Notification: required: - app_name diff --git a/lms/djangoapps/bulk_email/signals.py b/lms/djangoapps/bulk_email/signals.py index d45d0ae017bd..afac652debf9 100644 --- a/lms/djangoapps/bulk_email/signals.py +++ b/lms/djangoapps/bulk_email/signals.py @@ -41,7 +41,9 @@ def ace_email_sent_handler(sender, **kwargs): except user_model.DoesNotExist: user_id = None course_email = message.context.get('course_email', None) - course_id = course_email.course_id if course_email else None + course_id = message.context.get('course_id') + if not course_id: + course_id = course_email.course_id if course_email else None tracker.emit( 'edx.bulk_email.sent', { diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 60d45c15642d..0152d14ff01f 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -472,7 +472,7 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas 'edx.bulk_email.created', { 'course_id': str(course_email.course_id), - 'to_list': to_list, + 'to_list': [user_obj.get('email', '') for user_obj in to_list], 'total_recipients': total_recipients, } ) diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index be344dcb0d9b..0f8227e3201a 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -75,6 +75,7 @@ def send_ace_message(goal): 'email': user.email, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'course_name': course_name, + 'course_id': str(goal.course_key), 'days_per_week': goal.days_per_week, 'course_url': course_home_url, 'goals_unsubscribe_url': goals_unsubscribe_url, diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index 96e392c35d2a..25abcf80d486 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -286,13 +286,19 @@ def send_response_endorsed_on_thread_notification(self): response on his thread has been endorsed """ if self.creator.id != int(self.thread.user_id): - self._send_notification([self.thread.user_id], "response_endorsed_on_thread") + context = { + "email_content": clean_thread_html_body(self.comment.body) + } + self._send_notification([self.thread.user_id], "response_endorsed_on_thread", extra_context=context) def send_response_endorsed_notification(self): """ Sends a notification to the author of the response """ - self._send_notification([self.creator.id], "response_endorsed") + context = { + "email_content": clean_thread_html_body(self.comment.body) + } + self._send_notification([self.creator.id], "response_endorsed", extra_context=context) def send_new_thread_created_notification(self): """ diff --git a/lms/djangoapps/discussion/rest_api/tasks.py b/lms/djangoapps/discussion/rest_api/tasks.py index a87fafd4ca54..cbf438988975 100644 --- a/lms/djangoapps/discussion/rest_api/tasks.py +++ b/lms/djangoapps/discussion/rest_api/tasks.py @@ -64,7 +64,7 @@ def send_response_endorsed_notifications(thread_id, response_id, course_key_str, creator = User.objects.get(id=response.user_id) endorser = User.objects.get(id=endorsed_by) course = get_course_with_access(creator, 'load', course_key, check_if_enrolled=True) - notification_sender = DiscussionNotificationSender(thread, course, creator) + notification_sender = DiscussionNotificationSender(thread, course, creator, comment_id=response_id) # skip sending notification to author of thread if they are the same as the author of the response if response.user_id != thread.user_id: # sends notification to author of thread diff --git a/lms/djangoapps/discussion/rest_api/tests/test_tasks.py b/lms/djangoapps/discussion/rest_api/tests/test_tasks.py index 8efd5cd49cbd..ddfc120a8e4b 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_tasks.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_tasks.py @@ -663,6 +663,7 @@ def test_response_endorsed_notifications(self): 'post_title': 'test thread', 'course_name': self.course.display_name, 'sender_id': int(self.user_2.id), + 'email_content': 'dummy' } self.assertDictEqual(notification_data.context, expected_context) self.assertEqual(notification_data.content_url, _get_mfe_url(self.course.id, thread.id)) @@ -680,6 +681,7 @@ def test_response_endorsed_notifications(self): 'post_title': 'test thread', 'course_name': self.course.display_name, 'sender_id': int(response.user_id), + 'email_content': 'dummy' } self.assertDictEqual(notification_data.context, expected_context) self.assertEqual(notification_data.content_url, _get_mfe_url(self.course.id, thread.id)) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 0255e26e2a65..d9a301b07e7f 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -1674,18 +1674,31 @@ def get_proctored_exam_results(request, course_id): return JsonResponse({"status": success_status}) -@transaction.non_atomic_requests -@ensure_csrf_cookie -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -@require_course_permission(permissions.CAN_RESEARCH) -def get_anon_ids(request, course_id): +@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch') +@method_decorator(transaction.non_atomic_requests, name='dispatch') +class GetAnonIds(APIView): """ - Respond with 2-column CSV output of user-id, anonymized-user-id + Respond with 2-column CSV output of user-id, anonymized-user-id. + This API processes the incoming request to generate a CSV file containing + two columns: `user-id` and `anonymized-user-id`. The CSV is returned as a + response to the client. """ - report_type = _('Anonymized User IDs') - success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) - task_api.generate_anonymous_ids(request, course_id) - return JsonResponse({"status": success_status}) + permission_classes = (IsAuthenticated, permissions.InstructorPermission) + permission_name = permissions.CAN_RESEARCH + + @method_decorator(ensure_csrf_cookie) + @method_decorator(transaction.non_atomic_requests) + def post(self, request, course_id): + """ + Handle POST request to generate a CSV output. + + Returns: + Response: A CSV file with two columns: `user-id` and `anonymized-user-id`. + """ + report_type = _('Anonymized User IDs') + success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) + task_api.generate_anonymous_ids(request, course_id) + return JsonResponse({"status": success_status}) @require_POST diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index f25ea56c2ea7..14fe15c83c2e 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -31,7 +31,7 @@ re_path(r'^get_students_features(?P/csv)?$', api.get_students_features, name='get_students_features'), path('get_issued_certificates/', api.get_issued_certificates, name='get_issued_certificates'), path('get_students_who_may_enroll', api.GetStudentsWhoMayEnroll.as_view(), name='get_students_who_may_enroll'), - path('get_anon_ids', api.get_anon_ids, name='get_anon_ids'), + path('get_anon_ids', api.GetAnonIds.as_view(), name='get_anon_ids'), path('get_student_enrollment_status', api.get_student_enrollment_status, name="get_student_enrollment_status"), path('get_student_progress_url', api.StudentProgressUrl.as_view(), name='get_student_progress_url'), path('reset_student_attempts', api.reset_student_attempts, name='reset_student_attempts'), diff --git a/lms/djangoapps/learner_dashboard/config/waffle.py b/lms/djangoapps/learner_dashboard/config/waffle.py index 2195a2697269..cc63e8d5d13c 100644 --- a/lms/djangoapps/learner_dashboard/config/waffle.py +++ b/lms/djangoapps/learner_dashboard/config/waffle.py @@ -37,20 +37,3 @@ 'learner_dashboard.enable_masters_program_tab_view', __name__, ) - -# .. toggle_name: learner_dashboard.enable_b2c_subscriptions -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Waffle flag to enable new B2C Subscriptions Program data. -# This flag is used to decide whether we need to enable program subscription related properties in program listing -# and detail pages. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2023-04-13 -# .. toggle_target_removal_date: 2023-07-01 -# .. toggle_warning: When the flag is ON, the new B2C Subscriptions Program data will be enabled in program listing -# and detail pages. -# .. toggle_tickets: PON-79 -ENABLE_B2C_SUBSCRIPTIONS = WaffleFlag( - 'learner_dashboard.enable_b2c_subscriptions', - __name__, -) diff --git a/lms/djangoapps/learner_dashboard/programs.py b/lms/djangoapps/learner_dashboard/programs.py index d567a4b9a350..dc334c0ce34e 100644 --- a/lms/djangoapps/learner_dashboard/programs.py +++ b/lms/djangoapps/learner_dashboard/programs.py @@ -6,7 +6,6 @@ from abc import ABC, abstractmethod from urllib.parse import quote -from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.http import Http404 from django.template.loader import render_to_string @@ -18,7 +17,7 @@ from common.djangoapps.student.models import anonymous_id_for_user from common.djangoapps.student.roles import GlobalStaff -from lms.djangoapps.learner_dashboard.utils import b2c_subscriptions_enabled, program_tab_view_is_enabled +from lms.djangoapps.learner_dashboard.utils import program_tab_view_is_enabled from openedx.core.djangoapps.catalog.utils import get_programs from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.programs.models import ( @@ -32,9 +31,7 @@ get_industry_and_credit_pathways, get_program_and_course_data, get_program_marketing_url, - get_program_subscriptions_marketing_url, get_program_urls, - get_programs_subscription_data ) from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences from openedx.core.djangolib.markup import HTML @@ -60,30 +57,12 @@ def render_to_fragment(self, request, **kwargs): raise Http404 meter = ProgramProgressMeter(request.site, user, mobile_only=mobile_only) - is_user_b2c_subscriptions_enabled = b2c_subscriptions_enabled(mobile_only) - programs_subscription_data = ( - get_programs_subscription_data(user) - if is_user_b2c_subscriptions_enabled - else [] - ) - subscription_upsell_data = ( - { - 'marketing_url': get_program_subscriptions_marketing_url(), - 'minimum_price': settings.SUBSCRIPTIONS_MINIMUM_PRICE, - 'trial_length': settings.SUBSCRIPTIONS_TRIAL_LENGTH, - } - if is_user_b2c_subscriptions_enabled - else {} - ) context = { 'marketing_url': get_program_marketing_url(programs_config, mobile_only), 'programs': meter.engaged_programs, 'progress': meter.progress(), - 'programs_subscription_data': programs_subscription_data, - 'subscription_upsell_data': subscription_upsell_data, 'user_preferences': get_user_preferences(user), - 'is_user_b2c_subscriptions_enabled': is_user_b2c_subscriptions_enabled, 'mobile_only': bool(mobile_only) } html = render_to_string('learner_dashboard/programs_fragment.html', context) @@ -137,12 +116,6 @@ def render_to_fragment(self, request, program_uuid, **kwargs): # lint-amnesty, program_discussion_lti = ProgramDiscussionLTI(program_uuid, request) program_live_lti = ProgramLiveLTI(program_uuid, request) - is_user_b2c_subscriptions_enabled = b2c_subscriptions_enabled(mobile_only) - program_subscription_data = ( - get_programs_subscription_data(user, program_uuid) - if is_user_b2c_subscriptions_enabled - else [] - ) def program_tab_view_enabled() -> bool: return program_tab_view_is_enabled() and ( @@ -156,14 +129,11 @@ def program_tab_view_enabled() -> bool: 'urls': urls, 'user_preferences': get_user_preferences(user), 'program_data': program_data, - 'program_subscription_data': program_subscription_data, 'course_data': course_data, 'certificate_data': certificate_data, 'industry_pathways': industry_pathways, 'credit_pathways': credit_pathways, 'program_tab_view_enabled': program_tab_view_enabled(), - 'is_user_b2c_subscriptions_enabled': is_user_b2c_subscriptions_enabled, - 'subscriptions_trial_length': settings.SUBSCRIPTIONS_TRIAL_LENGTH, 'discussion_fragment': { 'configured': program_discussion_lti.is_configured, 'iframe': program_discussion_lti.render_iframe() diff --git a/lms/djangoapps/learner_dashboard/utils.py b/lms/djangoapps/learner_dashboard/utils.py index a604ba73786a..5e9c172fcb78 100644 --- a/lms/djangoapps/learner_dashboard/utils.py +++ b/lms/djangoapps/learner_dashboard/utils.py @@ -7,7 +7,6 @@ from common.djangoapps.student.roles import GlobalStaff from lms.djangoapps.learner_dashboard.config.waffle import ( - ENABLE_B2C_SUBSCRIPTIONS, ENABLE_MASTERS_PROGRAM_TAB_VIEW, ENABLE_PROGRAM_TAB_VIEW ) @@ -50,19 +49,3 @@ def is_enrolled_or_staff(request, program_uuid): except ObjectDoesNotExist: return False return True - - -def b2c_subscriptions_is_enabled() -> bool: - """ - Check if B2C program subscriptions flag is enabled. - """ - return ENABLE_B2C_SUBSCRIPTIONS.is_enabled() - - -def b2c_subscriptions_enabled(is_mobile=False) -> bool: - """ - Check whether B2C Subscriptions pages should be shown to user. - """ - if not is_mobile and b2c_subscriptions_is_enabled(): - return True - return False diff --git a/lms/envs/common.py b/lms/envs/common.py index 04a1753838ed..122ce7383bca 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3687,6 +3687,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # because that decision might happen in a later config file. (The headers to # allow is an application logic, and not site policy.) CORS_ALLOW_HEADERS = corsheaders_default_headers + ( + 'cache-control', + 'expires', + 'pragma', 'use-jwt-cookie', ) @@ -4688,7 +4691,6 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'enterprise_channel_worker', 'enterprise_access_worker', 'enterprise_subsidy_worker', - 'subscriptions_worker' ] # Setting for Open API key and prompts used by edx-enterprise. @@ -5382,17 +5384,6 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring AVAILABLE_DISCUSSION_TOURS = [] -######################## Subscriptions API SETTINGS ######################## -SUBSCRIPTIONS_ROOT_URL = "" -SUBSCRIPTIONS_API_PATH = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscription/" - -SUBSCRIPTIONS_LEARNER_HELP_CENTER_URL = None -SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscribe/" -SUBSCRIPTIONS_MANAGE_SUBSCRIPTION_URL = None -SUBSCRIPTIONS_MINIMUM_PRICE = '$39' -SUBSCRIPTIONS_TRIAL_LENGTH = 7 -SUBSCRIPTIONS_SERVICE_WORKER_USERNAME = 'subscriptions_worker' - ############## NOTIFICATIONS ############## NOTIFICATIONS_EXPIRY = 60 EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000 @@ -5475,6 +5466,10 @@ def _should_send_learning_badge_events(settings): 'learning-course-access-role-lifecycle': {'event_key_field': 'course_access_role_data.course_key', 'enabled': False}, }, + 'org.openedx.enterprise.learner_credit_course_enrollment.revoked.v1': { + 'learner-credit-course-enrollment-lifecycle': + {'event_key_field': 'learner_credit_course_enrollment.uuid', 'enabled': False}, + }, # CMS events. These have to be copied over here because cms.common adds some derived entries as well, # and the derivation fails if the keys are missing. If we ever fully decouple the lms and cms settings, # we can remove these. diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 611017962852..7a06f717996c 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -522,15 +522,9 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing ] course_access_role_removed_event_setting['learning-course-access-role-lifecycle']['enabled'] = True -######################## Subscriptions API SETTINGS ######################## -SUBSCRIPTIONS_ROOT_URL = "http://host.docker.internal:18750" -SUBSCRIPTIONS_API_PATH = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscription/" - -SUBSCRIPTIONS_LEARNER_HELP_CENTER_URL = None -SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscribe/" -SUBSCRIPTIONS_MANAGE_SUBSCRIPTION_URL = None -SUBSCRIPTIONS_MINIMUM_PRICE = '$39' -SUBSCRIPTIONS_TRIAL_LENGTH = 7 +lc_enrollment_revoked_setting = \ + EVENT_BUS_PRODUCER_CONFIG['org.openedx.enterprise.learner_credit_course_enrollment.revoked.v1'] +lc_enrollment_revoked_setting['learner-credit-course-enrollment-lifecycle']['enabled'] = True # API access management API_ACCESS_MANAGER_EMAIL = 'api-access@example.com' diff --git a/lms/envs/test.py b/lms/envs/test.py index 3c4bb9564927..a9e8aaf9f2e2 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -650,15 +650,6 @@ SURVEY_REPORT_ENABLE = True ANONYMOUS_SURVEY_REPORT = False -######################## Subscriptions API SETTINGS ######################## -SUBSCRIPTIONS_ROOT_URL = "http://localhost:18750" -SUBSCRIPTIONS_API_PATH = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscription/" - -SUBSCRIPTIONS_LEARNER_HELP_CENTER_URL = None -SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL = f"{SUBSCRIPTIONS_ROOT_URL}/api/v1/stripe-subscribe/" -SUBSCRIPTIONS_MANAGE_SUBSCRIPTION_URL = None -SUBSCRIPTIONS_MINIMUM_PRICE = '$39' -SUBSCRIPTIONS_TRIAL_LENGTH = 7 CSRF_TRUSTED_ORIGINS = ['.example.com'] CSRF_TRUSTED_ORIGINS_WITH_SCHEME = ['https://*.example.com'] diff --git a/lms/static/js/learner_dashboard/models/program_subscription_model.js b/lms/static/js/learner_dashboard/models/program_subscription_model.js deleted file mode 100644 index 18f30031f7a5..000000000000 --- a/lms/static/js/learner_dashboard/models/program_subscription_model.js +++ /dev/null @@ -1,86 +0,0 @@ -import Backbone from 'backbone'; -import moment from 'moment'; - -import DateUtils from 'edx-ui-toolkit/js/utils/date-utils'; -import StringUtils from 'edx-ui-toolkit/js/utils/string-utils'; - - -/** - * Model for Program Subscription Data. - */ -class ProgramSubscriptionModel extends Backbone.Model { - constructor({ context }, ...args) { - const { - subscriptionData: [data = {}], - programData: { subscription_prices }, - urls = {}, - userPreferences = {}, - subscriptionsTrialLength: trialLength = 7, - } = context; - - const priceInUSD = subscription_prices?.find(({ currency }) => currency === 'USD'); - - const subscriptionState = data.subscription_state?.toLowerCase() ?? ''; - const subscriptionPrice = StringUtils.interpolate( - gettext('${price}/month {currency}'), - { - price: parseFloat(priceInUSD?.price), - currency: priceInUSD?.currency, - } - ); - - const subscriptionUrl = - subscriptionState === 'active' - ? urls.manage_subscription_url - : urls.buy_subscription_url; - - const hasActiveTrial = false; - - const remainingDays = 0; - - const [currentPeriodEnd] = ProgramSubscriptionModel.formatDate( - data.current_period_end, - userPreferences - ); - const [trialEndDate, trialEndTime] = ['', '']; - - super( - { - hasActiveTrial, - currentPeriodEnd, - remainingDays, - subscriptionPrice, - subscriptionState, - subscriptionUrl, - trialEndDate, - trialEndTime, - trialLength, - }, - ...args - ); - } - - static formatDate(date, userPreferences) { - if (!date) { - return ['', '']; - } - - const userTimezone = ( - userPreferences.time_zone || moment?.tz?.guess?.() || 'UTC' - ); - const userLanguage = userPreferences['pref-lang'] || 'en'; - const context = { - datetime: date, - timezone: userTimezone, - language: userLanguage, - format: DateUtils.dateFormatEnum.shortDate, - }; - - const localDate = DateUtils.localize(context); - const localTime = ''; - - return [localDate, localTime]; - } -} - -export default ProgramSubscriptionModel; diff --git a/lms/static/js/learner_dashboard/program_list_factory.js b/lms/static/js/learner_dashboard/program_list_factory.js index 54333066414a..b9ff1c40191a 100644 --- a/lms/static/js/learner_dashboard/program_list_factory.js +++ b/lms/static/js/learner_dashboard/program_list_factory.js @@ -11,58 +11,18 @@ import HeaderView from './views/program_list_header_view'; function ProgramListFactory(options) { const progressCollection = new ProgressCollection(); - const subscriptionCollection = new Backbone.Collection(); if (options.userProgress) { progressCollection.set(options.userProgress); options.progressCollection = progressCollection; // eslint-disable-line no-param-reassign } - if (options.programsSubscriptionData.length) { - subscriptionCollection.set(options.programsSubscriptionData); - options.subscriptionCollection = subscriptionCollection; // eslint-disable-line no-param-reassign - } - if (options.programsData.length) { if (!options.mobileOnly) { new HeaderView({ context: options, }).render(); } - - const activeSubscriptions = options.programsSubscriptionData - // eslint-disable-next-line camelcase - .filter(({ subscription_state }) => subscription_state === 'active') - .sort((a, b) => new Date(b.created) - new Date(a.created)); - - // Sort programs so programs with active subscriptions are at the top - if (activeSubscriptions.length) { - // eslint-disable-next-line no-param-reassign - options.programsData = options.programsData - .map((programsData) => ({ - ...programsData, - subscriptionIndex: activeSubscriptions.findIndex( - // eslint-disable-next-line camelcase - ({ resource_id }) => resource_id === programsData.uuid, - ), - })) - .sort(({ subscriptionIndex: indexA }, { subscriptionIndex: indexB }) => { - switch (true) { - case indexA === -1 && indexB === -1: - // Maintain the original order for non-subscription programs - return 0; - case indexA === -1: - // Move non-subscription program to the end - return 1; - case indexB === -1: - // Keep non-subscription program to the end - return -1; - default: - // Sort by subscriptionIndex in ascending order - return indexA - indexB; - } - }); - } } new CollectionListView({ diff --git a/lms/static/js/learner_dashboard/spec/collection_list_view_spec.js b/lms/static/js/learner_dashboard/spec/collection_list_view_spec.js index c9c1c4d97bf2..1cd490447b0d 100644 --- a/lms/static/js/learner_dashboard/spec/collection_list_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/collection_list_view_spec.js @@ -1,7 +1,5 @@ /* globals setFixtures */ -import Backbone from 'backbone'; - import CollectionListView from '../views/collection_list_view'; import ProgramCardView from '../views/program_card_view'; import ProgramCollection from '../collections/program_collection'; @@ -11,7 +9,6 @@ describe('Collection List View', () => { let view = null; let programCollection; let progressCollection; - let subscriptionCollection; const context = { programsData: [ { @@ -101,21 +98,14 @@ describe('Collection List View', () => { not_started: 3, }, ], - programsSubscriptionData: [{ - resource_id: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', - subscription_state: 'active', - }], - isUserB2CSubscriptionsEnabled: false, }; beforeEach(() => { setFixtures('
'); programCollection = new ProgramCollection(context.programsData); progressCollection = new ProgressCollection(); - subscriptionCollection = new Backbone.Collection(context.programsSubscriptionData); progressCollection.set(context.userProgress); context.progressCollection = progressCollection; - context.subscriptionCollection = subscriptionCollection; view = new CollectionListView({ el: '.program-cards-container', diff --git a/lms/static/js/learner_dashboard/spec/course_card_view_spec.js b/lms/static/js/learner_dashboard/spec/course_card_view_spec.js index 5a0f18162868..91439c4a87a2 100644 --- a/lms/static/js/learner_dashboard/spec/course_card_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/course_card_view_spec.js @@ -17,10 +17,8 @@ describe('Course Card View', () => { programData, collectionCourseStatus, courseData: {}, - subscriptionData: [], urls: {}, userPreferences: {}, - isSubscriptionEligible: false, }; if (typeof collectionCourseStatus === 'undefined') { diff --git a/lms/static/js/learner_dashboard/spec/program_alert_list_view_spec.js b/lms/static/js/learner_dashboard/spec/program_alert_list_view_spec.js deleted file mode 100644 index 501cb9000483..000000000000 --- a/lms/static/js/learner_dashboard/spec/program_alert_list_view_spec.js +++ /dev/null @@ -1,58 +0,0 @@ -/* globals setFixtures */ - -import ProgramAlertListView from '../views/program_alert_list_view'; - -describe('Program Alert List View', () => { - let view = null; - const context = { - enrollmentAlerts: [{ title: 'Test Program' }], - trialEndingAlerts: [{ - title: 'Test Program', - hasActiveTrial: true, - currentPeriodEnd: 'May 8, 2023', - remainingDays: 2, - subscriptionPrice: '$100/month USD', - subscriptionState: 'active', - subscriptionUrl: null, - trialEndDate: 'Apr 20, 2023', - trialEndTime: '5:59 am', - trialLength: 7, - }], - pageType: 'programDetails', - }; - - beforeEach(() => { - setFixtures('
'); - view = new ProgramAlertListView({ - el: '.js-program-details-alerts', - context, - }); - view.render(); - }); - - afterEach(() => { - view.remove(); - }); - - it('should exist', () => { - expect(view).toBeDefined(); - }); - - it('should render no enrollement alert', () => { - expect(view.$('.alert:first .alert-heading').text().trim()).toEqual( - 'Enroll in a Test Program\'s course', - ); - expect(view.$('.alert:first .alert-message').text().trim()).toEqual( - 'You have an active subscription to the Test Program program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.', - ); - }); - - it('should render subscription trial is expiring alert', () => { - expect(view.$('.alert:last .alert-heading').text().trim()).toEqual( - 'Subscription trial expires in 2 days', - ); - expect(view.$('.alert:last .alert-message').text().trim()).toEqual( - 'Your Test Program trial will expire in 2 days at 5:59 am on Apr 20, 2023 and the card on file will be charged $100/month USD.', - ); - }); -}); diff --git a/lms/static/js/learner_dashboard/spec/program_card_view_spec.js b/lms/static/js/learner_dashboard/spec/program_card_view_spec.js index 290db60a4d0a..bf8a718f0a67 100644 --- a/lms/static/js/learner_dashboard/spec/program_card_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/program_card_view_spec.js @@ -42,7 +42,6 @@ describe('Program card View', () => { name: 'Wageningen University & Research', }, ], - subscriptionIndex: 1, }; const userProgress = [ { @@ -58,11 +57,6 @@ describe('Program card View', () => { not_started: 3, }, ]; - // eslint-disable-next-line no-undef - const subscriptionCollection = new Backbone.Collection([{ - resource_id: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8', - subscription_state: 'active', - }]); const progressCollection = new ProgressCollection(); const cardRenders = ($card) => { expect($card).toBeDefined(); @@ -80,8 +74,6 @@ describe('Program card View', () => { model: programModel, context: { progressCollection, - subscriptionCollection, - isUserB2CSubscriptionsEnabled: true, }, }); }); @@ -133,10 +125,6 @@ describe('Program card View', () => { view.remove(); view = new ProgramCardView({ model: programModel, - context: { - subscriptionCollection, - isUserB2CSubscriptionsEnabled: true, - }, }); cardRenders(view.$el); expect(view.$('.progress').length).toEqual(0); @@ -149,10 +137,6 @@ describe('Program card View', () => { programModel = new ProgramModel(programNoBanner); view = new ProgramCardView({ model: programModel, - context: { - subscriptionCollection, - isUserB2CSubscriptionsEnabled: true, - }, }); cardRenders(view.$el); expect(view.$el.find('.banner-image').attr('srcset')).toEqual(''); @@ -167,16 +151,8 @@ describe('Program card View', () => { programModel = new ProgramModel(programNoBanner); view = new ProgramCardView({ model: programModel, - context: { - subscriptionCollection, - isUserB2CSubscriptionsEnabled: true, - }, }); cardRenders(view.$el); expect(view.$el.find('.banner-image').attr('srcset')).toEqual(''); }); - - it('should render the subscription badge if subscription is active', () => { - expect(view.$('.subscription-badge .badge').html()?.trim()).toEqual('Subscribed'); - }); }); diff --git a/lms/static/js/learner_dashboard/spec/program_details_header_spec.js b/lms/static/js/learner_dashboard/spec/program_details_header_spec.js index d28d8f0bd3ee..862fb3f228d9 100644 --- a/lms/static/js/learner_dashboard/spec/program_details_header_spec.js +++ b/lms/static/js/learner_dashboard/spec/program_details_header_spec.js @@ -45,16 +45,6 @@ describe('Program Details Header View', () => { }, ], }, - subscriptionData: [ - { - trial_end: '1970-01-01T03:25:45Z', - current_period_end: '1970-06-03T07:12:04Z', - price: '100.00', - currency: 'USD', - subscription_state: 'active', - }, - ], - isSubscriptionEligible: true, }; beforeEach(() => { @@ -81,8 +71,4 @@ describe('Program Details Header View', () => { expect(view.$('.org-logo').attr('alt')) .toEqual(`${context.programData.authoring_organizations[0].name}'s logo`); }); - - it('should render the subscription badge if subscription is active', () => { - expect(view.$('.meta-info .badge').html().trim()).toEqual('Subscribed'); - }); }); diff --git a/lms/static/js/learner_dashboard/spec/program_details_sidebar_view_spec.js b/lms/static/js/learner_dashboard/spec/program_details_sidebar_view_spec.js index 60c877da8ad6..e1db3ddd181e 100644 --- a/lms/static/js/learner_dashboard/spec/program_details_sidebar_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/program_details_sidebar_view_spec.js @@ -1,9 +1,7 @@ /* globals setFixtures */ import Backbone from 'backbone'; -import moment from 'moment'; -import SubscriptionModel from '../models/program_subscription_model'; import ProgramSidebarView from '../views/program_details_sidebar_view'; describe('Program Progress View', () => { @@ -25,15 +23,13 @@ describe('Program Progress View', () => { "url": "/certificates/bed3980e67ca40f0b31e309d9dfe9e7e", "type": "course", "title": "Introduction to the Treatment of Urban Sewage" } ], - urls: {"program_listing_url": "/dashboard/programs/", "commerce_api_url": "/api/commerce/v0/baskets/", "track_selection_url": "/course_modes/choose/", "program_record_url": "/foo/bar", "buy_subscription_url": "/subscriptions", "orders_and_subscriptions_url": "/orders", "subscriptions_learner_help_center_url": "/learner"}, + urls: {"program_listing_url": "/dashboard/programs/", "commerce_api_url": "/api/commerce/v0/baskets/", "track_selection_url": "/course_modes/choose/"}, userPreferences: {"pref-lang": "en"} }; /* eslint-enable */ let programModel; let courseData; - let subscriptionData; let certificateCollection; - let isSubscriptionEligible; const testCircle = (progress) => { const $circle = view.$('.progress-circle'); @@ -53,55 +49,15 @@ describe('Program Progress View', () => { expect(parseInt($numbers.find('.total').html(), 10)).toEqual(total); }; - const testSubscriptionState = (state, heading, body) => { - isSubscriptionEligible = true; - subscriptionData.subscription_state = state; - // eslint-disable-next-line no-use-before-define - view = initView(); - // eslint-disable-next-line no-param-reassign - body += ' on the Orders and subscriptions page'; - - expect(view.$('.js-subscription-info')[0]).toBeInDOM(); - expect( - view.$('.js-subscription-info .divider-heading').text().trim(), - ).toEqual(heading); - expect( - view.$('.js-subscription-info .subscription-section p:nth-child(1)'), - ).toContainHtml(body); - expect( - view.$('.js-subscription-info .subscription-section p:nth-child(2)'), - ).toContainText( - /Need help\? Check out the.*Learner Help Center.*to troubleshoot issues or contact support/, - ); - expect( - view.$('.js-subscription-info .subscription-section p:nth-child(2) .subscription-link').attr('href'), - ).toEqual('/learner'); - }; - const initView = () => new ProgramSidebarView({ el: '.js-program-sidebar', model: programModel, courseModel: courseData, - subscriptionModel: new SubscriptionModel({ - context: { - programData: { - subscription_eligible: isSubscriptionEligible, - subscription_prices: [{ - price: '100.00', - currency: 'USD', - }], - }, - subscriptionData: [subscriptionData], - urls: data.urls, - userPreferences: data.userPreferences, - }, - }), certificateCollection, industryPathways: data.industryPathways, creditPathways: data.creditPathways, programTabViewEnabled: false, urls: data.urls, - isSubscriptionEligible, }); beforeEach(() => { @@ -109,14 +65,6 @@ describe('Program Progress View', () => { programModel = new Backbone.Model(data.programData); courseData = new Backbone.Model(data.courseData); certificateCollection = new Backbone.Collection(data.certificateData); - isSubscriptionEligible = false; - subscriptionData = { - trial_end: '1970-01-01T03:25:45Z', - current_period_end: '1970-06-03T07:12:04Z', - price: '100.00', - currency: 'USD', - subscription_state: 'pre', - }; }); afterEach(() => { @@ -203,69 +151,14 @@ describe('Program Progress View', () => { el: '.js-program-sidebar', model: programModel, courseModel: courseData, - subscriptionModel: new SubscriptionModel({ - context: { - programData: { - subscription_eligible: isSubscriptionEligible, - subscription_prices: [{ - price: '100.00', - currency: 'USD', - }], - }, - subscriptionData: [subscriptionData], - urls: data.urls, - userPreferences: data.userPreferences, - }, - }), certificateCollection, industryPathways: [], creditPathways: [], programTabViewEnabled: false, urls: data.urls, - isSubscriptionEligible, }); expect(emptyView.$('.program-credit-pathways .divider-heading')).toHaveLength(0); expect(emptyView.$('.program-industry-pathways .divider-heading')).toHaveLength(0); }); - - it('should not render subscription info if program is not subscription eligible', () => { - view = initView(); - expect(view.$('.js-subscription-info')[0]).not.toBeInDOM(); - }); - - it('should render subscription info if program is subscription eligible', () => { - testSubscriptionState( - 'pre', - 'Inactive subscription', - 'If you had a subscription previously, your payment history is still available', - ); - }); - - it('should render active trial subscription info if subscription is active with trial', () => { - subscriptionData.trial_end = moment().add(3, 'days').utc().format( - 'YYYY-MM-DDTHH:mm:ss[Z]', - ); - testSubscriptionState( - 'active', - 'Trial subscription', - 'View your receipts or modify your subscription', - ); - }); - - it('should render active subscription info if subscription active', () => { - testSubscriptionState( - 'active', - 'Active subscription', - 'View your receipts or modify your subscription', - ); - }); - - it('should render inactive subscription info if subscription inactive', () => { - testSubscriptionState( - 'inactive', - 'Inactive subscription', - 'Restart your subscription for $100/month USD. Your payment history is still available', - ); - }); }); diff --git a/lms/static/js/learner_dashboard/spec/program_details_view_spec.js b/lms/static/js/learner_dashboard/spec/program_details_view_spec.js index feaf72526192..a3be0f10815d 100644 --- a/lms/static/js/learner_dashboard/spec/program_details_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/program_details_view_spec.js @@ -7,11 +7,6 @@ describe('Program Details View', () => { let view = null; const options = { programData: { - subscription_eligible: false, - subscription_prices: [{ - price: '100.00', - currency: 'USD', - }], subtitle: '', overview: '', weeks_to_complete: null, @@ -468,24 +463,11 @@ describe('Program Details View', () => { }, ], }, - subscriptionData: [ - { - trial_end: '1970-01-01T03:25:45Z', - current_period_end: '1970-06-03T07:12:04Z', - price: '100.00', - currency: 'USD', - subscription_state: 'pre', - }, - ], urls: { program_listing_url: '/dashboard/programs/', commerce_api_url: '/api/commerce/v0/baskets/', track_selection_url: '/course_modes/choose/', program_record_url: 'http://credentials.example.com/records/programs/UUID', - buy_subscription_url: '/subscriptions', - manage_subscription_url: '/orders', - subscriptions_learner_help_center_url: '/learner', - orders_and_subscriptions_url: '/orders', }, userPreferences: { 'pref-lang': 'en', @@ -513,59 +495,9 @@ describe('Program Details View', () => { }, ], programTabViewEnabled: false, - isUserB2CSubscriptionsEnabled: false, }; const data = options.programData; - const testSubscriptionState = (state, heading, body, trial = false) => { - const subscriptionData = { - ...options.subscriptionData[0], - subscription_state: state, - }; - if (trial) { - subscriptionData.trial_end = moment().add(3, 'days').utc().format( - 'YYYY-MM-DDTHH:mm:ss[Z]', - ); - } - // eslint-disable-next-line no-use-before-define - view = initView({ - // eslint-disable-next-line no-undef - programData: $.extend({}, options.programData, { - subscription_eligible: true, - }), - isUserB2CSubscriptionsEnabled: true, - subscriptionData: [subscriptionData], - }); - view.render(); - expect(view.$('.upgrade-subscription')[0]).toBeInDOM(); - expect(view.$('.upgrade-subscription .upgrade-button')) - .toContainText(heading); - expect(view.$('.upgrade-subscription .subscription-info-brief')) - .toContainText(body); - }; - - const testSubscriptionSunsetting = (state, heading, body) => { - const subscriptionData = { - ...options.subscriptionData[0], - subscription_state: state, - }; - // eslint-disable-next-line no-use-before-define - view = initView({ - // eslint-disable-next-line no-undef - programData: $.extend({}, options.programData, { - subscription_eligible: false, - }), - isUserB2CSubscriptionsEnabled: true, - subscriptionData: [subscriptionData], - }); - view.render(); - expect(view.$('.upgrade-subscription')[0]).not.toBeInDOM(); - expect(view.$('.upgrade-subscription .upgrade-button')).not - .toContainText(heading); - expect(view.$('.upgrade-subscription .subscription-info-brief')).not - .toContainText(body); - }; - const initView = (updates) => { // eslint-disable-next-line no-undef const viewOptions = $.extend({}, options, updates); @@ -730,37 +662,4 @@ describe('Program Details View', () => { properties, ); }); - - it('should not render the get subscription link if program is not active', () => { - testSubscriptionSunsetting( - 'pre', - 'Start 7-day free trial', - '$100/month USD subscription after trial ends. Cancel anytime.', - ); - }); - - it('should not render appropriate subscription text when subscription is active with trial', () => { - testSubscriptionSunsetting( - 'active', - 'Manage my subscription', - 'Trial ends', - true, - ); - }); - - it('should not render appropriate subscription text when subscription is active', () => { - testSubscriptionSunsetting( - 'active', - 'Manage my subscription', - 'Your next billing date is', - ); - }); - - it('should not render appropriate subscription text when subscription is inactive', () => { - testSubscriptionSunsetting( - 'inactive', - 'Restart my subscription', - '$100/month USD subscription. Cancel anytime.', - ); - }); }); diff --git a/lms/static/js/learner_dashboard/spec/program_list_header_view_spec.js b/lms/static/js/learner_dashboard/spec/program_list_header_view_spec.js index 4a663fc1f825..5e1c09bfe463 100644 --- a/lms/static/js/learner_dashboard/spec/program_list_header_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/program_list_header_view_spec.js @@ -13,27 +13,14 @@ describe('Program List Header View', () => { { uuid: '5b234e3c-3a2e-472e-90db-6f51501dc86c', title: 'edX Demonstration Program', - subscription_eligible: null, - subscription_prices: [], detail_url: '/dashboard/programs/5b234e3c-3a2e-472e-90db-6f51501dc86c/', }, { uuid: 'b90d70d5-f981-4508-bdeb-5b792d930c03', title: 'Test Program', - subscription_eligible: true, - subscription_prices: [{ price: '500.00', currency: 'USD' }], detail_url: '/dashboard/programs/b90d70d5-f981-4508-bdeb-5b792d930c03/', }, ], - programsSubscriptionData: [ - { - id: 'eeb25640-9741-4c11-963c-8a27337f217c', - resource_id: 'b90d70d5-f981-4508-bdeb-5b792d930c03', - trial_end: '2022-04-20T05:59:42Z', - current_period_end: '2023-05-08T05:59:42Z', - subscription_state: 'active', - }, - ], userProgress: [ { uuid: '5b234e3c-3a2e-472e-90db-6f51501dc86c', @@ -50,13 +37,9 @@ describe('Program List Header View', () => { all_unenrolled: true, }, ], - isUserB2CSubscriptionsEnabled: true, }; beforeEach(() => { - context.subscriptionCollection = new Backbone.Collection( - context.programsSubscriptionData, - ); context.progressCollection = new ProgressCollection( context.userProgress, ); @@ -78,18 +61,4 @@ describe('Program List Header View', () => { it('should render the program heading', () => { expect(view.$('h2:first').text().trim()).toEqual('My programs'); }); - - it('should render a program alert', () => { - expect( - view.$('.js-program-list-alerts .alert .alert-heading').html().trim(), - ).toEqual('Enroll in a Test Program\'s course'); - expect( - view.$('.js-program-list-alerts .alert .alert-message'), - ).toContainHtml( - 'According to our records, you are not enrolled in any courses included in your Test Program program subscription. Enroll in a course from the Program Details page.', - ); - expect( - view.$('.js-program-list-alerts .alert .view-button').attr('href'), - ).toEqual('/dashboard/programs/b90d70d5-f981-4508-bdeb-5b792d930c03/'); - }); }); diff --git a/lms/static/js/learner_dashboard/spec/sidebar_view_spec.js b/lms/static/js/learner_dashboard/spec/sidebar_view_spec.js index 04c936908e3c..e96369abb63d 100644 --- a/lms/static/js/learner_dashboard/spec/sidebar_view_spec.js +++ b/lms/static/js/learner_dashboard/spec/sidebar_view_spec.js @@ -6,12 +6,6 @@ describe('Sidebar View', () => { let view = null; const context = { marketingUrl: 'https://www.example.org/programs', - subscriptionUpsellData: { - marketing_url: 'https://www.example.org/program-subscriptions', - minimum_price: '$39', - trial_length: 7, - }, - isUserB2CSubscriptionsEnabled: true, }; beforeEach(() => { @@ -32,10 +26,6 @@ describe('Sidebar View', () => { expect(view).toBeDefined(); }); - it('should not render the subscription upsell section', () => { - expect(view.$('.js-subscription-upsell')[0]).not.toBeInDOM(); - }); - it('should load the exploration panel given a marketing URL', () => { expect(view.$('.program-advertise .advertise-message').html().trim()) .toEqual( @@ -49,10 +39,6 @@ describe('Sidebar View', () => { view.remove(); view = new SidebarView({ el: '.sidebar', - context: { - isUserB2CSubscriptionsEnabled: true, - subscriptionUpsellData: context.subscriptionUpsellData, - }, }); view.render(); const $ad = view.$el.find('.program-advertise'); diff --git a/lms/static/js/learner_dashboard/views/course_card_view.js b/lms/static/js/learner_dashboard/views/course_card_view.js index 72028d6d95f5..dce9c7a384e6 100644 --- a/lms/static/js/learner_dashboard/views/course_card_view.js +++ b/lms/static/js/learner_dashboard/views/course_card_view.js @@ -9,8 +9,6 @@ import ExpiredNotificationView from './expired_notification_view'; import CourseEnrollView from './course_enroll_view'; import EntitlementView from './course_entitlement_view'; -import SubscriptionModel from '../models/program_subscription_model'; - import pageTpl from '../../../templates/learner_dashboard/course_card.underscore'; class CourseCardView extends Backbone.View { @@ -27,9 +25,6 @@ class CourseCardView extends Backbone.View { this.enrollModel = new EnrollModel(); if (options.context) { this.urlModel = new Backbone.Model(options.context.urls); - this.subscriptionModel = new SubscriptionModel({ - context: options.context, - }); this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url'); } this.context = options.context || {}; @@ -93,8 +88,6 @@ class CourseCardView extends Backbone.View { this.upgradeMessage = new UpgradeMessageView({ $el: $upgradeMessage, model: this.model, - subscriptionModel: this.subscriptionModel, - isSubscriptionEligible: this.context.isSubscriptionEligible, }); $certStatus.remove(); diff --git a/lms/static/js/learner_dashboard/views/program_alert_list_view.js b/lms/static/js/learner_dashboard/views/program_alert_list_view.js deleted file mode 100644 index 6c42d85444ea..000000000000 --- a/lms/static/js/learner_dashboard/views/program_alert_list_view.js +++ /dev/null @@ -1,89 +0,0 @@ -import Backbone from 'backbone'; - -import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils'; -import StringUtils from 'edx-ui-toolkit/js/utils/string-utils'; - -import warningIcon from '../../../images/warning-icon.svg'; -import programAlertTpl from '../../../templates/learner_dashboard/program_alert_list_view.underscore'; - -class ProgramAlertListView extends Backbone.View { - constructor(options) { - const defaults = { - el: '.js-program-details-alerts', - }; - // eslint-disable-next-line prefer-object-spread - super(Object.assign({}, defaults, options)); - } - - initialize({ context }) { - this.tpl = HtmlUtils.template(programAlertTpl); - this.enrollmentAlerts = context.enrollmentAlerts || []; - this.trialEndingAlerts = context.trialEndingAlerts || []; - this.pageType = context.pageType; - this.render(); - } - - render() { - const data = { - alertList: this.getAlertList(), - warningIcon, - }; - HtmlUtils.setHtml(this.$el, this.tpl(data)); - } - - getAlertList() { - const alertList = this.enrollmentAlerts.map( - ({ title: programName, url }) => ({ - url, - // eslint-disable-next-line no-undef - urlText: gettext('View program'), - title: StringUtils.interpolate( - // eslint-disable-next-line no-undef - gettext('Enroll in a {programName}\'s course'), - { programName }, - ), - message: this.pageType === 'programDetails' - ? StringUtils.interpolate( - // eslint-disable-next-line no-undef - gettext('You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.'), - { programName }, - ) - : HtmlUtils.interpolateHtml( - // eslint-disable-next-line no-undef - gettext('According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.'), - { - programName, - i_start: HtmlUtils.HTML(''), - i_end: HtmlUtils.HTML(''), - }, - ), - }), - ); - return alertList.concat(this.trialEndingAlerts.map( - ({ title: programName, remainingDays, ...data }) => ({ - title: StringUtils.interpolate( - remainingDays < 1 - // eslint-disable-next-line no-undef - ? gettext('Subscription trial expires in less than 24 hours') - // eslint-disable-next-line no-undef - : ngettext('Subscription trial expires in {remainingDays} day', 'Subscription trial expires in {remainingDays} days', remainingDays), - { remainingDays }, - ), - message: StringUtils.interpolate( - remainingDays < 1 - // eslint-disable-next-line no-undef - ? gettext('Your {programName} trial will expire at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.') - // eslint-disable-next-line no-undef - : ngettext('Your {programName} trial will expire in {remainingDays} day at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', 'Your {programName} trial will expire in {remainingDays} days at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', remainingDays), - { - programName, - remainingDays, - ...data, - }, - ), - }), - )); - } -} - -export default ProgramAlertListView; diff --git a/lms/static/js/learner_dashboard/views/program_card_view.js b/lms/static/js/learner_dashboard/views/program_card_view.js index 1a5a05313521..f4715e25388f 100644 --- a/lms/static/js/learner_dashboard/views/program_card_view.js +++ b/lms/static/js/learner_dashboard/views/program_card_view.js @@ -30,10 +30,6 @@ class ProgramCardView extends Backbone.View { uuid: this.model.get('uuid'), }); } - this.isSubscribed = ( - context.isUserB2CSubscriptionsEnabled && - this.model.get('subscriptionIndex') > -1 - ) ?? false; this.render(); } @@ -45,7 +41,6 @@ class ProgramCardView extends Backbone.View { this.getProgramProgress(), { orgList: orgList.join(' '), - isSubscribed: this.isSubscribed, }, ); diff --git a/lms/static/js/learner_dashboard/views/program_details_sidebar_view.js b/lms/static/js/learner_dashboard/views/program_details_sidebar_view.js index fea4ebd809dc..fa8ccb629b44 100644 --- a/lms/static/js/learner_dashboard/views/program_details_sidebar_view.js +++ b/lms/static/js/learner_dashboard/views/program_details_sidebar_view.js @@ -30,9 +30,7 @@ class ProgramDetailsSidebarView extends Backbone.View { this.industryPathways = options.industryPathways; this.creditPathways = options.creditPathways; this.programModel = options.model; - this.subscriptionModel = options.subscriptionModel; this.programTabViewEnabled = options.programTabViewEnabled; - this.isSubscriptionEligible = options.isSubscriptionEligible; this.urls = options.urls; this.render(); } @@ -42,14 +40,12 @@ class ProgramDetailsSidebarView extends Backbone.View { const data = $.extend( {}, this.model.toJSON(), - this.subscriptionModel.toJSON(), { programCertificate: this.programCertificate ? this.programCertificate.toJSON() : {}, industryPathways: this.industryPathways, creditPathways: this.creditPathways, programTabViewEnabled: this.programTabViewEnabled, - isSubscriptionEligible: this.isSubscriptionEligible, arrowUprightIcon, ...this.urls, }, diff --git a/lms/static/js/learner_dashboard/views/program_details_view.js b/lms/static/js/learner_dashboard/views/program_details_view.js index 220840c182e4..006d30c59b05 100644 --- a/lms/static/js/learner_dashboard/views/program_details_view.js +++ b/lms/static/js/learner_dashboard/views/program_details_view.js @@ -10,10 +10,6 @@ import CourseCardView from './course_card_view'; // eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member import HeaderView from './program_header_view'; import SidebarView from './program_details_sidebar_view'; -import AlertListView from './program_alert_list_view'; - -// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member -import SubscriptionModel from '../models/program_subscription_model'; import launchIcon from '../../../images/launch-icon.svg'; import restartIcon from '../../../images/restart-icon.svg'; @@ -27,7 +23,6 @@ class ProgramDetailsView extends Backbone.View { el: '.js-program-details-wrapper', events: { 'click .complete-program': 'trackPurchase', - 'click .js-subscription-cta': 'trackSubscriptionCTA', }, }; // eslint-disable-next-line prefer-object-spread @@ -46,9 +41,6 @@ class ProgramDetailsView extends Backbone.View { this.certificateCollection = new Backbone.Collection( this.options.certificateData, ); - this.subscriptionModel = new SubscriptionModel({ - context: this.options, - }); this.completedCourseCollection = new CourseCardCollection( this.courseData.get('completed') || [], this.options.userPreferences, @@ -61,11 +53,6 @@ class ProgramDetailsView extends Backbone.View { this.courseData.get('not_started') || [], this.options.userPreferences, ); - this.subscriptionEventParams = { - label: this.options.programData.title, - program_uuid: this.options.programData.uuid, - }; - this.options.isSubscriptionEligible = this.getIsSubscriptionEligible(); this.render(); @@ -76,7 +63,6 @@ class ProgramDetailsView extends Backbone.View { pageName: 'program_dashboard', linkCategory: 'green_upgrade', }); - this.trackSubscriptionEligibleProgramView(); } static getUrl(base, programData) { @@ -107,7 +93,6 @@ class ProgramDetailsView extends Backbone.View { creditPathways: this.options.creditPathways, discussionFragment: this.options.discussionFragment, live_fragment: this.options.live_fragment, - isSubscriptionEligible: this.options.isSubscriptionEligible, launchIcon, restartIcon, }; @@ -115,7 +100,6 @@ class ProgramDetailsView extends Backbone.View { data = $.extend( data, this.programModel.toJSON(), - this.subscriptionModel.toJSON(), ); HtmlUtils.setHtml(this.$el, this.tpl(data)); this.postRender(); @@ -126,20 +110,6 @@ class ProgramDetailsView extends Backbone.View { model: new Backbone.Model(this.options), }); - if (this.options.isSubscriptionEligible) { - const { enrollmentAlerts, trialEndingAlerts } = this.getAlerts(); - - if (enrollmentAlerts.length || trialEndingAlerts.length) { - this.alertListView = new AlertListView({ - context: { - enrollmentAlerts, - trialEndingAlerts, - pageType: 'programDetails', - }, - }); - } - } - if (this.remainingCourseCollection.length > 0) { new CollectionListView({ el: '.js-course-list-remaining', @@ -178,12 +148,10 @@ class ProgramDetailsView extends Backbone.View { el: '.js-program-sidebar', model: this.programModel, courseModel: this.courseData, - subscriptionModel: this.subscriptionModel, certificateCollection: this.certificateCollection, industryPathways: this.options.industryPathways, creditPathways: this.options.creditPathways, programTabViewEnabled: this.options.programTabViewEnabled, - isSubscriptionEligible: this.options.isSubscriptionEligible, urls: this.options.urls, }); let hasIframe = false; @@ -197,59 +165,6 @@ class ProgramDetailsView extends Backbone.View { }).bind(this); } - getIsSubscriptionEligible() { - const courseCollections = [ - this.completedCourseCollection, - this.inProgressCourseCollection, - ]; - const isSomeCoursePurchasable = courseCollections.some((collection) => ( - collection.some((course) => ( - course.get('upgrade_url') - && !(course.get('expired') === true) - )) - )); - const programPurchasedWithoutSubscription = ( - this.subscriptionModel.get('subscriptionState') !== 'active' - && this.subscriptionModel.get('subscriptionState') !== 'inactive' - && !isSomeCoursePurchasable - && this.remainingCourseCollection.length === 0 - ); - - const isSubscriptionActiveSunsetting = ( - this.subscriptionModel.get('subscriptionState') === 'active' - ) - - return ( - this.options.isUserB2CSubscriptionsEnabled - && isSubscriptionActiveSunsetting - && !programPurchasedWithoutSubscription - ); - } - - getAlerts() { - const alerts = { - enrollmentAlerts: [], - trialEndingAlerts: [], - }; - if (this.subscriptionModel.get('subscriptionState') === 'active') { - if (this.courseData.get('all_unenrolled')) { - alerts.enrollmentAlerts.push({ - title: this.programModel.get('title'), - }); - } - if ( - this.subscriptionModel.get('remainingDays') <= 7 - && this.subscriptionModel.get('hasActiveTrial') - ) { - alerts.trialEndingAlerts.push({ - title: this.programModel.get('title'), - ...this.subscriptionModel.toJSON(), - }); - } - } - return alerts; - } - trackPurchase() { const data = this.options.programData; window.analytics.track('edx.bi.user.dashboard.program.purchase', { @@ -258,37 +173,6 @@ class ProgramDetailsView extends Backbone.View { uuid: data.uuid, }); } - - trackSubscriptionCTA() { - const state = this.subscriptionModel.get('subscriptionState'); - - if (state === 'active') { - window.analytics.track( - 'edx.bi.user.subscription.program-detail-page.manage.clicked', - this.subscriptionEventParams, - ); - } else { - const isNewSubscription = state !== 'inactive'; - window.analytics.track( - 'edx.bi.user.subscription.program-detail-page.subscribe.clicked', - { - category: `${this.options.programData.variant} bundle`, - is_new_subscription: isNewSubscription, - is_trial_eligible: isNewSubscription, - ...this.subscriptionEventParams, - }, - ); - } - } - - trackSubscriptionEligibleProgramView() { - if (this.options.isSubscriptionEligible) { - window.analytics.track( - 'edx.bi.user.subscription.program-detail-page.viewed', - this.subscriptionEventParams, - ); - } - } } export default ProgramDetailsView; diff --git a/lms/static/js/learner_dashboard/views/program_header_view.js b/lms/static/js/learner_dashboard/views/program_header_view.js index 2fd8e9fe5190..acb3c876cad0 100644 --- a/lms/static/js/learner_dashboard/views/program_header_view.js +++ b/lms/static/js/learner_dashboard/views/program_header_view.js @@ -42,22 +42,11 @@ class ProgramHeaderView extends Backbone.View { return logo; } - getIsSubscribed() { - const isSubscriptionEligible = this.model.get('isSubscriptionEligible'); - const subscriptionData = this.model.get('subscriptionData')?.[0]; - - return ( - isSubscriptionEligible && - subscriptionData?.subscription_state === 'active' - ); - } - render() { // eslint-disable-next-line no-undef const data = $.extend(this.model.toJSON(), { breakpoints: this.breakpoints, logo: this.getLogo(), - isSubscribed: this.getIsSubscribed(), }); if (this.model.get('programData')) { diff --git a/lms/static/js/learner_dashboard/views/program_list_header_view.js b/lms/static/js/learner_dashboard/views/program_list_header_view.js index 6520caf08615..98e628cefae4 100644 --- a/lms/static/js/learner_dashboard/views/program_list_header_view.js +++ b/lms/static/js/learner_dashboard/views/program_list_header_view.js @@ -2,10 +2,6 @@ import Backbone from 'backbone'; import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils'; -import AlertListView from './program_alert_list_view'; - -import SubscriptionModel from '../models/program_subscription_model'; - import programListHeaderTpl from '../../../templates/learner_dashboard/program_list_header_view.underscore'; class ProgramListHeaderView extends Backbone.View { @@ -19,76 +15,11 @@ class ProgramListHeaderView extends Backbone.View { initialize({ context }) { this.context = context; this.tpl = HtmlUtils.template(programListHeaderTpl); - this.programAndSubscriptionData = context.programsData - .map((programData) => ({ - programData, - subscriptionData: context.subscriptionCollection - ?.findWhere({ - resource_id: programData.uuid, - subscription_state: 'active', - }) - ?.toJSON(), - })) - .filter(({ subscriptionData }) => !!subscriptionData); this.render(); } render() { HtmlUtils.setHtml(this.$el, this.tpl(this.context)); - this.postRender(); - } - - postRender() { - if (this.context.isUserB2CSubscriptionsEnabled) { - const enrollmentAlerts = this.getEnrollmentAlerts(); - const trialEndingAlerts = this.getTrialEndingAlerts(); - - if (enrollmentAlerts.length || trialEndingAlerts.length) { - this.alertListView = new AlertListView({ - el: '.js-program-list-alerts', - context: { - enrollmentAlerts, - trialEndingAlerts, - pageType: 'programList', - }, - }); - } - } - } - - getEnrollmentAlerts() { - return this.programAndSubscriptionData - .map(({ programData, subscriptionData }) => - this.context.progressCollection?.findWhere({ - uuid: programData.uuid, - all_unenrolled: true, - }) ? { - title: programData.title, - url: programData.detail_url, - } : null - ) - .filter(Boolean); - } - - getTrialEndingAlerts() { - return this.programAndSubscriptionData - .map(({ programData, subscriptionData }) => { - const subscriptionModel = new SubscriptionModel({ - context: { - programData, - subscriptionData: [subscriptionData], - userPreferences: this.context?.userPreferences, - }, - }); - return ( - subscriptionModel.get('remainingDays') <= 7 && - subscriptionModel.get('hasActiveTrial') && { - title: programData.title, - ...subscriptionModel.toJSON(), - } - ); - }) - .filter(Boolean); } } diff --git a/lms/static/js/learner_dashboard/views/sidebar_view.js b/lms/static/js/learner_dashboard/views/sidebar_view.js index 3359eac1b429..520efbe29f03 100644 --- a/lms/static/js/learner_dashboard/views/sidebar_view.js +++ b/lms/static/js/learner_dashboard/views/sidebar_view.js @@ -10,9 +10,6 @@ class SidebarView extends Backbone.View { constructor(options) { const defaults = { el: '.sidebar', - events: { - 'click .js-subscription-upsell-cta ': 'trackSubscriptionUpsellCTA', - }, }; // eslint-disable-next-line prefer-object-spread super(Object.assign({}, defaults, options)); @@ -33,12 +30,6 @@ class SidebarView extends Backbone.View { context: this.context, }); } - - trackSubscriptionUpsellCTA() { - window.analytics.track( - 'edx.bi.user.subscription.program-dashboard.upsell.clicked', - ); - } } export default SidebarView; diff --git a/lms/static/js/learner_dashboard/views/subscription_upsell_view.js b/lms/static/js/learner_dashboard/views/subscription_upsell_view.js deleted file mode 100644 index 3c085aaf7e7b..000000000000 --- a/lms/static/js/learner_dashboard/views/subscription_upsell_view.js +++ /dev/null @@ -1,30 +0,0 @@ -import Backbone from 'backbone'; - -import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils'; - -import subscriptionUpsellTpl from '../../../templates/learner_dashboard/subscription_upsell_view.underscore'; - -class SubscriptionUpsellView extends Backbone.View { - constructor(options) { - const defaults = { - el: '.js-subscription-upsell', - }; - // eslint-disable-next-line prefer-object-spread - super(Object.assign({}, defaults, options)); - } - - initialize(options) { - this.tpl = HtmlUtils.template(subscriptionUpsellTpl); - this.subscriptionUpsellModel = new Backbone.Model( - options.subscriptionUpsellData, - ); - this.render(); - } - - render() { - const data = this.subscriptionUpsellModel.toJSON(); - HtmlUtils.setHtml(this.$el, this.tpl(data)); - } -} - -export default SubscriptionUpsellView; diff --git a/lms/static/js/learner_dashboard/views/upgrade_message_view.js b/lms/static/js/learner_dashboard/views/upgrade_message_view.js index 07d1b9522e95..c8ad3632861f 100644 --- a/lms/static/js/learner_dashboard/views/upgrade_message_view.js +++ b/lms/static/js/learner_dashboard/views/upgrade_message_view.js @@ -3,18 +3,12 @@ import Backbone from 'backbone'; import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils'; import upgradeMessageTpl from '../../../templates/learner_dashboard/upgrade_message.underscore'; -import upgradeMessageSubscriptionTpl from '../../../templates/learner_dashboard/upgrade_message_subscription.underscore'; import trackECommerceEvents from '../../commerce/track_ecommerce_events'; class UpgradeMessageView extends Backbone.View { initialize(options) { - if (options.isSubscriptionEligible) { - this.messageTpl = HtmlUtils.template(upgradeMessageSubscriptionTpl); - } else { - this.messageTpl = HtmlUtils.template(upgradeMessageTpl); - } + this.messageTpl = HtmlUtils.template(upgradeMessageTpl); this.$el = options.$el; - this.subscriptionModel = options.subscriptionModel; this.render(); const courseUpsellButtons = this.$el.find('.program_dashboard_course_upsell_button'); @@ -30,7 +24,6 @@ class UpgradeMessageView extends Backbone.View { const data = $.extend( {}, this.model.toJSON(), - this.subscriptionModel.toJSON(), ); HtmlUtils.setHtml(this.$el, this.messageTpl(data)); } diff --git a/lms/static/sass/views/_program-details.scss b/lms/static/sass/views/_program-details.scss index 9056f04a13d7..f5a6eb62b50b 100644 --- a/lms/static/sass/views/_program-details.scss +++ b/lms/static/sass/views/_program-details.scss @@ -90,21 +90,6 @@ $btn-color-primary: $primary-dark; } } -.program-details-alerts { - .page-banner { - margin: 0; - padding: 0 0 48px; - gap: 24px; - } -} - -.program-details-tab-alerts { - .page-banner { - margin: 0; - gap: 24px; - } -} - // CSS for April 2017 version of Program Details Page .program-details { .window-wrap { @@ -449,42 +434,6 @@ $btn-color-primary: $primary-dark; } } - .upgrade-subscription { - margin: 16px 0 10px; - row-gap: 16px; - column-gap: 24px; - } - - .subscription-icon-launch { - width: 22.5px; - height: 22.5px; - margin-inline-start: 8px; - } - - .subscription-icon-restart { - width: 22.5px; - height: 22.5px; - margin-inline-end: 8px; - } - - .subscription-icon-arrow-upright { - display: inline-flex; - align-items: center; - width: 15px; - height: 15px; - margin-inline-start: 8px; - } - - .subscription-info-brief { - font-size: 0.9375em; - color: $gray-500; - } - - .subscription-info-upsell { - margin-top: 0.25rem; - font-size: 0.8125em; - } - .program-course-card { width: 100%; padding: 15px 15px 15px 0px; @@ -681,24 +630,6 @@ $btn-color-primary: $primary-dark; .program-sidebar { padding: 40px 40px 40px 0px; - .program-record,.subscription-info { - text-align: left; - padding-bottom: 2em; - } - - .subscription-section { - display: flex; - flex-direction: column; - gap: 16px; - color: #414141; - - .subscription-link { - color: inherit; - text-decoration: none; - border-bottom: 1px solid currentColor; - } - } - .sidebar-section { font-size: 0.9375em; width: auto; diff --git a/lms/static/sass/views/_program-list.scss b/lms/static/sass/views/_program-list.scss index 23f9a78b7c0d..d05e2eb2859b 100644 --- a/lms/static/sass/views/_program-list.scss +++ b/lms/static/sass/views/_program-list.scss @@ -39,13 +39,6 @@ .program-cards-container { @include grid-container(); padding-top: 32px; - - .subscription-badge { - position: absolute; - top: 8px; - left: 8px; - z-index: 10; - } } .sidebar { diff --git a/lms/templates/instructor/instructor_dashboard_2/special_exams.html b/lms/templates/instructor/instructor_dashboard_2/special_exams.html index 194c0cdcb018..2658af0bc70e 100644 --- a/lms/templates/instructor/instructor_dashboard_2/special_exams.html +++ b/lms/templates/instructor/instructor_dashboard_2/special_exams.html @@ -7,7 +7,7 @@
% if section_data.get('mfe_view_url'):
- +
% else: % if section_data.get('escalation_email'): diff --git a/lms/templates/learner_dashboard/program_card.underscore b/lms/templates/learner_dashboard/program_card.underscore index c9364d6ca2c7..de98c952dd15 100644 --- a/lms/templates/learner_dashboard/program_card.underscore +++ b/lms/templates/learner_dashboard/program_card.underscore @@ -61,8 +61,3 @@
-<% if (isSubscribed) { %> -
- <%- gettext('Subscribed') %> -
-<% } %> diff --git a/lms/templates/learner_dashboard/program_details_fragment.html b/lms/templates/learner_dashboard/program_details_fragment.html index 7aff07a6a3ac..70571ca80ff8 100644 --- a/lms/templates/learner_dashboard/program_details_fragment.html +++ b/lms/templates/learner_dashboard/program_details_fragment.html @@ -14,7 +14,6 @@ <%static:webpack entry="ProgramDetailsFactory"> ProgramDetailsFactory({ programData: ${program_data | n, dump_js_escaped_json}, - subscriptionData: ${program_subscription_data | n, dump_js_escaped_json}, courseData: ${course_data | n, dump_js_escaped_json}, certificateData: ${certificate_data | n, dump_js_escaped_json}, urls: ${urls | n, dump_js_escaped_json}, @@ -22,8 +21,6 @@ industryPathways: ${industry_pathways | n, dump_js_escaped_json}, creditPathways: ${credit_pathways | n, dump_js_escaped_json}, programTabViewEnabled: ${program_tab_view_enabled | n, dump_js_escaped_json}, - isUserB2CSubscriptionsEnabled: ${is_user_b2c_subscriptions_enabled | n, dump_js_escaped_json}, - subscriptionsTrialLength: ${subscriptions_trial_length | n, dump_js_escaped_json}, discussionFragment: ${discussion_fragment, | n, dump_js_escaped_json}, live_fragment: ${live_fragment, | n, dump_js_escaped_json} }); diff --git a/lms/templates/learner_dashboard/program_details_sidebar.underscore b/lms/templates/learner_dashboard/program_details_sidebar.underscore index cab7aad04b75..0e05ae9b9a08 100644 --- a/lms/templates/learner_dashboard/program_details_sidebar.underscore +++ b/lms/templates/learner_dashboard/program_details_sidebar.underscore @@ -8,50 +8,6 @@ <% } %> -<% if (isSubscriptionEligible) { %> - -<% } %>