From ede9102e331c69968164eb5616b47ae91d296e7f Mon Sep 17 00:00:00 2001 From: Awais Qureshi Date: Fri, 30 Aug 2024 16:19:48 +0500 Subject: [PATCH 01/21] feat: upgrading get_anon_ids api to drf compatible (12) (#35345) * feat: upgrading simple api to drf compatible. --- lms/djangoapps/instructor/views/api.py | 33 ++++++++++++++------- lms/djangoapps/instructor/views/api_urls.py | 2 +- 2 files changed, 24 insertions(+), 11 deletions(-) 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'), From ecd31b32a6281b6ad9fd1b4dc5b48a166b8cefdc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:20:58 +0000 Subject: [PATCH 02/21] fix(deps): update dependency webpack to v5.94.0 [security] --- package-lock.json | 50 ++++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2a83bf7a648..87c75af69044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4223,24 +4223,6 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" }, - "node_modules/@types/eslint": { - "version": "8.56.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.6.tgz", - "integrity": "sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -4628,10 +4610,11 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", "peerDependencies": { "acorn": "^8" } @@ -25127,20 +25110,20 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -25295,9 +25278,10 @@ } }, "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -25310,6 +25294,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -25322,6 +25307,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -25330,6 +25316,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -25347,6 +25334,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", "engines": { "node": ">=6" } From 638bc5dd8526ba139f94e6c869d80f87f88bd0df Mon Sep 17 00:00:00 2001 From: Juliana Kang Date: Fri, 30 Aug 2024 09:20:01 -0400 Subject: [PATCH 03/21] chore: Update LMS openapi spec yml file (#35400) --- docs/lms-openapi.yaml | 141 +++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 76 deletions(-) diff --git a/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 38c52e737e19..9fa03436e13f 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 @@ -6649,6 +6626,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 +6678,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 +6863,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 +6916,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 +7037,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 +7212,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 +11753,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 From 311da82ff9909dcbf71ffbf0b697e045ede0f894 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Fri, 30 Aug 2024 10:40:21 -0400 Subject: [PATCH 04/21] feat: DEPR USE-JWT-COOKIE header - Part 1 (#35401) This repo is no longer using USE-JWT-COOKIE header, since it has the required edx-drf-extensions>10.2.0, where it was fully removed. This removes all uses of the header, except updating CORS_ALLOW_HEADERS, which can't be done before all MFEs and other callers stop sending the header. See "[DEPR]: USE-JWT-COOKIE header" for more details: - https://github.com/openedx/edx-drf-extensions/issues/371 --- openedx/core/djangoapps/user_authn/tests/test_cookies.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openedx/core/djangoapps/user_authn/tests/test_cookies.py b/openedx/core/djangoapps/user_authn/tests/test_cookies.py index a90f20f19469..8a7841b3b980 100644 --- a/openedx/core/djangoapps/user_authn/tests/test_cookies.py +++ b/openedx/core/djangoapps/user_authn/tests/test_cookies.py @@ -74,9 +74,6 @@ def _copy_cookies_to_request(self, response, request): for key, val in response.cookies.items() } - def _set_use_jwt_cookie_header(self, request): - request.META['HTTP_USE_JWT_COOKIE'] = 'true' - def _assert_recreate_jwt_from_cookies(self, response, can_recreate): """ If can_recreate is True, verifies that a JWT can be properly recreated @@ -133,7 +130,6 @@ def test_set_logged_in_deprecated_cookies(self): @patch.dict("django.conf.settings.FEATURES", {"DISABLE_SET_JWT_COOKIES_FOR_TESTS": False}) def test_set_logged_in_jwt_cookies(self): setup_login_oauth_client() - self._set_use_jwt_cookie_header(self.request) response = cookies_api.set_logged_in_cookies(self.request, HttpResponse(), self.user) self._assert_cookies_present(response, cookies_api.ALL_LOGGED_IN_COOKIE_NAMES) self._assert_consistent_expires(response, num_of_unique_expires=2) @@ -153,7 +149,6 @@ def test_delete_and_are_logged_in_cookies_set(self): @patch.dict("django.conf.settings.FEATURES", {"DISABLE_SET_JWT_COOKIES_FOR_TESTS": False}) def test_refresh_jwt_cookies(self): setup_login_oauth_client() - self._set_use_jwt_cookie_header(self.request) response = cookies_api.get_response_with_refreshed_jwt_cookies(self.request, self.user) data = json.loads(response.content.decode('utf8').replace("'", '"')) assert data['success'] is True From 92cc3bf8083116ddc3ff290e5b0fda0caca1f8e4 Mon Sep 17 00:00:00 2001 From: Zachary Hancock Date: Fri, 30 Aug 2024 10:59:18 -0400 Subject: [PATCH 05/21] style: constrain exam iframe to avoid scroll problems (#35396) --- .../instructor/instructor_dashboard_2/special_exams.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'): From bfee53b9b1f7497399add0f1209c15f5bf55b810 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Fri, 30 Aug 2024 11:53:35 -0700 Subject: [PATCH 06/21] feat: Update CORS allowed headers for compat. w/ axios-cache-interceptor (#35402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://axios-cache-interceptor.js.org/config/request-specifics#cache-cachetakeover cache.cacheTakeover (default true): you need to make sure Cache-Control, Pragma and Expires headers are included into your server’s Access-Control-Allow-Headers CORS configuration. --- lms/envs/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lms/envs/common.py b/lms/envs/common.py index 04a1753838ed..2d31cba98218 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', ) From ec34753043964df9427224e6aaad96c5f8c951a3 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Sat, 31 Aug 2024 00:46:02 +0530 Subject: [PATCH 07/21] fix: update library v2 search index synchronously (#35367) Discarded components need to be removed from index in sync to make sure that users see the latest updated data. --- openedx/core/djangoapps/content/search/handlers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/content/search/handlers.py b/openedx/core/djangoapps/content/search/handlers.py index ba0e8c1a1680..94ac02ea1606 100644 --- a/openedx/core/djangoapps/content/search/handlers.py +++ b/openedx/core/djangoapps/content/search/handlers.py @@ -136,7 +136,13 @@ def content_library_updated_handler(**kwargs) -> None: log.error("Received null or incorrect data for event") return - update_content_library_index_docs.delay(str(content_library_data.library_key)) + # Update content library index synchronously to make sure that search index is updated before + # the frontend invalidates/refetches index. + # Currently, this is only required to make sure that removed/discarded components are removed + # from the search index and displayed to user properly. If it becomes a performance bottleneck + # for other update operations other than discard, we can update CONTENT_LIBRARY_UPDATED event + # to include a parameter which can help us decide if the task needs to run sync or async. + update_content_library_index_docs.apply(args=[str(content_library_data.library_key)]) @receiver(CONTENT_OBJECT_TAGS_CHANGED) From c96949eeaaf00485f245de8e9cdd52e692248596 Mon Sep 17 00:00:00 2001 From: MueezKhan246 <93375917+MueezKhan246@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:16:52 +0000 Subject: [PATCH 08/21] feat: Upgrade Python dependency edx-enterprise altered decrypted_secret to be encrypted and made credentials nullable Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 4a2f87e652b8..289f6a53be92 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ celery>=5.2.2,<6.0.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.23.14 +edx-enterprise==4.23.15 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 1c612fab6741..86bc4dbf840f 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.14 +edx-enterprise==4.23.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index e6fd96e3be28..d18028e3ef5c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.14 +edx-enterprise==4.23.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index c3dcafee9343..571343e90436 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.14 +edx-enterprise==4.23.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 925fe517c858..553cb86f5640 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.14 +edx-enterprise==4.23.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From e668790e11a2895a449d1218813f3ded4490c5f8 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Khan Date: Mon, 2 Sep 2024 14:17:13 +0500 Subject: [PATCH 09/21] chore: Convert annotatable block sass variables to css variables --- xmodule/assets/annotatable/_display.scss | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/xmodule/assets/annotatable/_display.scss b/xmodule/assets/annotatable/_display.scss index 124011e2bd2b..9aaa8649c6c5 100644 --- a/xmodule/assets/annotatable/_display.scss +++ b/xmodule/assets/annotatable/_display.scss @@ -9,7 +9,7 @@ @import 'bootstrap/scss/variables'; @import 'lms/theme/variables-v1'; -$annotatable--border-color: $gray-l3; +$annotatable--border-color: var(--gray-l3); $annotatable--body-font-size: em(14); .annotatable-wrapper { @@ -116,18 +116,18 @@ $annotatable--body-font-size: em(14); border: 1px solid #333; border-radius: 1em; background-color: rgba(0, 0, 0, 0.85); - color: $white; + color: var(--white); -webkit-font-smoothing: antialiased; .ui-tooltip-titlebar { font-size: em(16); color: inherit; background-color: transparent; - padding: ($baseline/4) ($baseline/2); + padding: calc((var(--baseline)/4)) calc((var(--baseline)/2)); border: none; .ui-tooltip-title { - padding: ($baseline/4) 0; + padding: calc((var(--baseline)/4)) 0; border-bottom: 2px solid #333; font-weight: bold; } @@ -139,7 +139,7 @@ $annotatable--body-font-size: em(14); .ui-state-hover { color: inherit; - border: 1px solid $gray-l3; + border: 1px solid var(--gray-l3); } } @@ -148,7 +148,7 @@ $annotatable--body-font-size: em(14); font-size: em(14); text-align: left; font-weight: 400; - padding: 0 ($baseline/2) ($baseline/2) ($baseline/2); + padding: 0 calc((var(--baseline)/2)) calc((var(--baseline)/2)) calc((var(--baseline)/2)); background-color: transparent; border-color: transparent; } @@ -163,11 +163,11 @@ $annotatable--body-font-size: em(14); max-width: 375px; .ui-tooltip-content { - padding: 0 ($baseline/2); + padding: 0 calc((var(--baseline)/2)); .annotatable-comment { display: block; - margin: 0 0 ($baseline/2) 0; + margin: 0 0 calc((var(--baseline)/2)) 0; max-height: 225px; overflow: auto; line-height: normal; @@ -176,7 +176,7 @@ $annotatable--body-font-size: em(14); .annotatable-reply { display: block; border-top: 2px solid #333; - padding: ($baseline/4) 0; + padding: calc((var(--baseline)/4)) 0; margin: 0; text-align: center; } @@ -190,7 +190,7 @@ $annotatable--body-font-size: em(14); left: 50%; height: 0; width: 0; - margin-left: -($baseline/4); + margin-left: calc(-1 * (var(--baseline) / 4)); border: 10px solid transparent; border-top-color: rgba(0, 0, 0, 0.85); } From 14d2c4e967ca46db4a2230695e40b647b8faaf89 Mon Sep 17 00:00:00 2001 From: Muhammad Sameer Amin <35958006+sameeramin@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:55:09 +0500 Subject: [PATCH 10/21] feat: skip `migrations_are_in_sync` test --- common/djangoapps/util/tests/test_db.py | 4 ++++ 1 file changed, 4 insertions(+) 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): """ From ff2afe19df704296dffdc65dc0491643d0b77292 Mon Sep 17 00:00:00 2001 From: sameeramin <35958006+sameeramin@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:00:48 +0000 Subject: [PATCH 11/21] feat: Upgrade Python dependency edx-enterprise reverts changes from #566 Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 289f6a53be92..6eff301ca188 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ celery>=5.2.2,<6.0.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.23.15 +edx-enterprise==4.23.19 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 86bc4dbf840f..f0a9075a18d7 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.15 +edx-enterprise==4.23.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index d18028e3ef5c..db7760477bae 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.15 +edx-enterprise==4.23.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 571343e90436..5ff7a74dbc87 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.15 +edx-enterprise==4.23.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 553cb86f5640..be7f8305ea84 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.15 +edx-enterprise==4.23.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 4a12de7851ddc39c371e7ea4fb98316b8fc7d136 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 3 Sep 2024 10:40:32 -0700 Subject: [PATCH 12/21] refactor: remove unused JS requirement in CMS (#34545) --- cms/envs/common.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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' From dd3a7aad8dd59fe75f0c96ffd9b7cea6248d9a21 Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Wed, 4 Sep 2024 00:56:56 -0700 Subject: [PATCH 13/21] fix: update bulk email created event data (#35412) --- lms/djangoapps/bulk_email/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } ) From 1ad9fd296757ec768105af6e13d922d66edcb731 Mon Sep 17 00:00:00 2001 From: sameeramin <35958006+sameeramin@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:22:19 +0000 Subject: [PATCH 14/21] feat: Upgrade Python dependency edx-enterprise added migrations to remove client_id and client_secret from canvas Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 6eff301ca188..1aa5e88a6f9c 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ celery>=5.2.2,<6.0.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.23.19 +edx-enterprise==4.23.20 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index f0a9075a18d7..c4da179ad7c8 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.19 +edx-enterprise==4.23.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index db7760477bae..7196b0d1d7c6 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.19 +edx-enterprise==4.23.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 5ff7a74dbc87..746bf686e139 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.19 +edx-enterprise==4.23.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index be7f8305ea84..8c11a4857db9 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.19 +edx-enterprise==4.23.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 96a6176a82ef0b07bef12b45101c859ce8779403 Mon Sep 17 00:00:00 2001 From: katrinan029 <71999631+katrinan029@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:27:57 +0000 Subject: [PATCH 15/21] feat: Upgrade Python dependency edx-enterprise chore: version bump Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 1aa5e88a6f9c..c96cb6b578e1 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ celery>=5.2.2,<6.0.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.23.20 +edx-enterprise==4.24.0 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c4da179ad7c8..25e6e8556e16 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.20 +edx-enterprise==4.24.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 7196b0d1d7c6..531eb6b00be4 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.20 +edx-enterprise==4.24.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 746bf686e139..8037d38b1a6e 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.20 +edx-enterprise==4.24.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 8c11a4857db9..646af2b7a076 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.23.20 +edx-enterprise==4.24.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 51d538cbe744cfea968cbc17d280045baa2c0e3d Mon Sep 17 00:00:00 2001 From: Juliana Kang Date: Wed, 4 Sep 2024 14:01:45 -0400 Subject: [PATCH 16/21] fix: Remove B2C Subscriptions (#35303) REV-3697 --- .../entitlements/rest_api/v1/permissions.py | 10 -- .../rest_api/v1/tests/test_views.py | 158 ------------------ .../entitlements/rest_api/v1/throttles.py | 21 --- .../entitlements/rest_api/v1/urls.py | 7 +- .../entitlements/rest_api/v1/views.py | 80 +-------- common/djangoapps/entitlements/tasks.py | 40 ----- docs/lms-openapi.yaml | 13 -- .../learner_dashboard/config/waffle.py | 17 -- lms/djangoapps/learner_dashboard/programs.py | 32 +--- lms/djangoapps/learner_dashboard/utils.py | 17 -- lms/envs/common.py | 12 -- lms/envs/devstack.py | 10 -- lms/envs/test.py | 9 - .../models/program_subscription_model.js | 86 ---------- .../learner_dashboard/program_list_factory.js | 40 ----- .../spec/collection_list_view_spec.js | 10 -- .../spec/course_card_view_spec.js | 2 - .../spec/program_alert_list_view_spec.js | 58 ------- .../spec/program_card_view_spec.js | 24 --- .../spec/program_details_header_spec.js | 14 -- .../spec/program_details_sidebar_view_spec.js | 109 +----------- .../spec/program_details_view_spec.js | 101 ----------- .../spec/program_list_header_view_spec.js | 31 ---- .../spec/sidebar_view_spec.js | 14 -- .../views/course_card_view.js | 7 - .../views/program_alert_list_view.js | 89 ---------- .../views/program_card_view.js | 5 - .../views/program_details_sidebar_view.js | 4 - .../views/program_details_view.js | 116 ------------- .../views/program_header_view.js | 11 -- .../views/program_list_header_view.js | 69 -------- .../learner_dashboard/views/sidebar_view.js | 9 - .../views/subscription_upsell_view.js | 30 ---- .../views/upgrade_message_view.js | 9 +- lms/static/sass/views/_program-details.scss | 69 -------- lms/static/sass/views/_program-list.scss | 7 - .../learner_dashboard/program_card.underscore | 5 - .../program_details_fragment.html | 3 - .../program_details_sidebar.underscore | 44 ----- .../program_details_tab_view.underscore | 5 +- .../program_details_view.underscore | 4 +- .../program_header_view.underscore | 5 - .../program_list_header_view.underscore | 1 - .../learner_dashboard/programs_fragment.html | 3 - .../subscription_upsell_view.underscore | 20 --- .../upgrade_message_subscription.underscore | 21 --- .../credentials/tests/test_utils.py | 35 ---- openedx/core/djangoapps/credentials/utils.py | 57 ------- .../djangoapps/programs/tests/test_utils.py | 100 ----------- openedx/core/djangoapps/programs/utils.py | 75 +-------- 50 files changed, 9 insertions(+), 1709 deletions(-) delete mode 100644 common/djangoapps/entitlements/rest_api/v1/throttles.py delete mode 100644 lms/static/js/learner_dashboard/models/program_subscription_model.js delete mode 100644 lms/static/js/learner_dashboard/spec/program_alert_list_view_spec.js delete mode 100644 lms/static/js/learner_dashboard/views/program_alert_list_view.js delete mode 100644 lms/static/js/learner_dashboard/views/subscription_upsell_view.js delete mode 100644 lms/templates/learner_dashboard/subscription_upsell_view.underscore delete mode 100644 lms/templates/learner_dashboard/upgrade_message_subscription.underscore 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/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 9fa03436e13f..5e9afcc6d370 100644 --- a/docs/lms-openapi.yaml +++ b/docs/lms-openapi.yaml @@ -5277,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 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 2d31cba98218..a6c8e810d206 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -4691,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. @@ -5385,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 diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 611017962852..890164b0bd49 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -522,16 +522,6 @@ 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 - # API access management API_ACCESS_MANAGER_EMAIL = 'api-access@example.com' API_ACCESS_FROM_EMAIL = 'api-requests@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/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) { %> - -<% } %>