Compare commits

...

1034 Commits

Author SHA1 Message Date
Omar Al-Ithawi
398330fa07 feat: include paragon in atlas pull (#1145)
This pull request is part of the [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) which is sparked by the [Translation Infrastructure update OEP-58](https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0058-arch-translations-management.html#specification).
2023-07-25 11:21:54 -04:00
renovate[bot]
f92fc8c3a5 fix(deps): update dependency @popperjs/core to v2.11.8 2023-07-25 14:01:38 +00:00
renovate[bot]
5e072949d6 chore(deps): update dependency @edx/frontend-build to v12.9.3 2023-07-25 11:25:32 +00:00
Rebecca Graber
2d132f114c feat: upgrade pact (#1141)
Upgrade pact to 11
2023-07-17 12:47:29 -04:00
alangsto
c73ef26d8e feat: add segment event for lti modal launch (#1140) 2023-07-13 13:56:25 -04:00
ayesha waris
97ca7fe6aa fix: sidebar state remains open for all users (#1139) 2023-07-13 15:30:16 +05:00
Ben Warzeski
e95a59c6c8 fix: modal hook name for unit iframe modal signal (#1138) 2023-07-10 15:26:56 -04:00
Peter Kulko
5f9c441cd2 fix: added Paragon translations (#1136) 2023-07-10 14:25:56 -04:00
Ben Warzeski
2e641ac6c9 Bw/unit splitup (#1134)
* refactor: break Unit component into smaller unit-tested parts

* feat: save scroll position on video fullscreen exit

* chore: remove swap file
2023-07-10 10:29:56 -04:00
alangsto
22937918ab feat: add component to iframe LTI launch (#1135) 2023-07-06 14:57:39 -04:00
ihor-romaniuk
714f5d452c fix: save scroll position on exit from video xblock fullscreen mode 2023-07-06 08:43:01 -04:00
ayesha waris
8ac9745261 fix: modifies sidebar state such that it remains open (#1131)
* fix: modifies sidebar state such that it remains open

* refactor: removed localstorage for discussions sideba
2023-06-27 14:05:46 +05:00
Zachary Hancock
340580cb41 chore: update exams lib (#1130) 2023-06-22 09:19:43 -04:00
Leangseu Kim
5a99ca5c91 fix: breadcrumb jump nav styling 2023-06-08 09:19:45 -04:00
Ben Warzeski
9943df49e4 feat: allow clipboard write to xblock iframes (#1117) 2023-06-06 10:09:33 -04:00
Jenkins
855474d406 chore(i18n): update translations 2023-06-04 17:09:52 -04:00
Ghassan Maslamani
a78496a3f6 fix: sync LMS_BASE_URL for bookmark API if changed
This change makes it possible to use the latest  LMS_BASE_API
  if it was changed because of dynamic config API, which is the
  default case of tutor.

  This changes closes openedx/wg-build-test-release/issues/270

   Fixes that are simlar to this
  - gradebook openedx/frontend-app-gradebook/pull/290
  - course authoring openedx/frontend-app-course-authoring/pull/389
2023-05-31 15:11:34 +01:00
Jansen Kantor
79b65dadca fix: gracefully handle 403 responses in tab loading (#1111) 2023-05-24 11:38:40 -04:00
Bilal Qamar
fc8f5d43e8 feat: upgraded to node v18, added .nvmrc and updated workflows (#1084)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* fix: add workaround in json file

* build: remove jestEnv line from json

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* fix: add workaround in json file

* build: remove jestEnv line from json

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated packages

* refactor: updated jest & fixed failing tests

* refactor: updated lmsPact failing test cases

* refactor: updated frontend-build version

* Merge branch master of github.com:edx/frontend-app-learning into bilalqamar95/node-v18-upgrade

---------

Co-authored-by: mashal-m <mashal.malik@arbisoft.com>
2023-05-23 18:39:34 +05:00
Zachary Hancock
6232f40a74 feat: update special-exams-lib (#1113) 2023-05-16 15:08:46 -04:00
David Joy
bc0ff1ce65 chore: bumping frontend-platform version to get userId logging
frontend-platform has a new feature to include the userId (if it exists) when logging an error to the logging service.  We want that.
2023-05-16 09:44:29 -04:00
David Joy
5997b29cee fix: logging an error when unit iframe fails to load
Right now we log nothing to the logging service when a unit iframe fails to load.  The ErrorPage that’s shown isn’t using the ErrorBoundary, so we had no indication that something went wrong.  This solves the problem closer to the source where the error originates.
2023-05-16 09:44:29 -04:00
Omar Al-Ithawi
d2de0632cd feat: add experimental atlas to pull_translations (#1093)
This is an experimental off-by-defualt feature for moving the translation files ouside the repos.

Run `OPENEDX_ATLAS_PULL=true make translations` to use atlas to pull translations instead of transifex.

Refs: FC-12 OEP-58
2023-05-09 10:03:42 -04:00
Zachary Hancock
922cc2187a fix: update exams lib to fix download click bug (#1110) 2023-05-09 09:23:40 -04:00
Sagirov Eugeniy
d9539796b5 chore: update frontend-platform version to v4.2.0 2023-05-02 14:34:30 -03:00
alangsto
e0acb501eb chore: upgrade frontend-lib-special-exams version (#1107) 2023-04-26 08:52:03 -04:00
Asad Ali
a03ffe2724 fix: fix links under contenttools (#1096) 2023-04-26 13:42:16 +05:00
Jenkins
cbdf7ce064 chore(i18n): update translations 2023-04-23 17:09:46 -04:00
Zachary Hancock
7184e85b2b feat: update exams library (#1103) 2023-04-21 12:09:29 -04:00
Emad Rad
b5321d01e4 feat: Persian Language added to messages (#989)
feat: fa_IR added to transifex_langs

feat: Persian translations added

Co-authored-by: Leangseu Kim <lkim@edx.org>
Co-authored-by: leangseu-edx <83240113+leangseu-edx@users.noreply.github.com>
2023-04-18 14:03:06 -04:00
Yoiber
6c8ab1a4c9 chore(i18n): add more languages (#1063)
* chore(i18n): add more languages

* chore(i18n): Pylint fixed
2023-04-18 12:41:30 -04:00
Varsha
01f9d8f50b feat: fetch exam access token (#1083)
* feat: fetch exam access token

* build: update frontend lib special exams version
2023-04-17 14:59:05 -04:00
Zachary Hancock
764befd4bd feat: exams svc should not be enabled by default (#1100) 2023-04-12 10:36:21 -04:00
Zachary Hancock
7317c9424a feat: update special-exams lib (#1098) 2023-04-10 09:46:21 -04:00
alangsto
d897663b73 feat: upgrade special exams version and add required config values (#1097) 2023-04-06 09:55:06 -04:00
Muhammad Adeel Tajamul
2e4eb158f2 feat: added url param to open discussion sidebar (#1092) 2023-04-04 13:33:35 +05:00
Jenkins
35b229bd1b chore(i18n): update translations 2023-04-02 17:09:43 -04:00
Muhammad Adeel Tajamul
4ebd569792 feat: added open/close state of discussion sidebar in local storage (#1086) 2023-03-28 15:39:00 +05:00
lunyachek
52235ebc1c feat: create component to decode params 2023-03-27 14:54:42 -04:00
Jenkins
aa380e8619 chore(i18n): update translations 2023-03-26 17:09:41 -04:00
lunyachek
4cf0c7f4d7 feat: Add border for active tab in course navigation at Live page 2023-03-22 10:36:33 -04:00
alangsto
743650a99e chore: pin frontend lib special exams version (#1088) 2023-03-17 13:35:26 -04:00
Muhammad Adeel Tajamul
39d89bee9e fix: discussion sidebar loads very slow (#1081) 2023-03-13 05:40:23 +05:00
Jenkins
a601e431b2 chore(i18n): update translations 2023-03-12 17:09:40 -04:00
Muhammad Adeel Tajamul
7519bbe28e fix: copy link for discussion sidebar not working in chrome (#1079) 2023-03-10 06:01:24 +05:00
alangsto
4b90dcbfc3 feat: update special exams version (#1080) 2023-03-09 10:45:47 -05:00
Zachary Hancock
54cb52cb6d feat: update special-exams library (#1078) 2023-03-08 15:14:39 -05:00
renovate[bot]
6dbd3f49dd fix(deps): update dependency @edx/paragon to v20.28.4 2023-03-01 11:25:35 +00:00
renovate[bot]
678502bb40 fix(deps): update dependency @edx/brand to v1.2.0 2023-03-01 07:19:50 +00:00
renovate[bot]
bf77fc7ca1 fix(deps): update dependency query-string to v7.1.3 2023-03-01 02:08:11 +00:00
renovate[bot]
421a9a5d2b fix(deps): update dependency @edx/frontend-lib-special-exams to v2.2.1 2023-02-28 22:45:58 +00:00
Feanil Patel
dfe44cae56 build: Updating a missing workflow file add-depr-ticket-to-depr-board.yml.
The .github/workflows/add-depr-ticket-to-depr-board.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 10:37:18 -05:00
Feanil Patel
a88571dae8 build: Creating a missing workflow file add-remove-label-on-comment.yml.
The .github/workflows/add-remove-label-on-comment.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 10:37:18 -05:00
Feanil Patel
a4ea334692 build: Creating a missing workflow file self-assign-issue.yml.
The .github/workflows/self-assign-issue.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 10:37:18 -05:00
Adam Stankiewicz
97a1cb4ffc chore: upgrade @edx/frontend-platform to v3.4.1 (#1071)
* chore: upgrade @edx/frontend-platform to v3.4.0
* chore: upgrade to frontend-platform v3.4.1
2023-02-28 09:18:28 -05:00
Jenkins
5166bfe056 chore(i18n): update translations 2023-02-26 16:09:40 -05:00
Varsha
33e3765b19 build: add exams url to envs (#1066) 2023-02-22 11:40:24 -05:00
Jenkins
a13e7d7389 chore(i18n): update translations 2023-02-19 16:09:38 -05:00
Isaac Lee
a4ea1b54a4 fix: exams with no due date now display exam type (#1064)
* fix: exams with no due date now display exam type
2023-02-16 15:16:19 -05:00
Eugene Dyudyunov
cd430ebb5d fix: first section celebration
Fix the first section celebration modal showing logic.

On Nutmeg+ it's shown only after the page reload or after going directly
to the second section from the course home. Going through the course
with the Next/Previous buttons has no effect (which worked on Maple).

Notes:
- the weekly goal has the same showing logic, but I assume that is
correct behavior so no changes are added for it in this commit.
- showing a celebration modal for the first section completion when
going directly to the first unit of the second section seems to be a bug
(reproduces on Maple too)
2023-02-14 16:54:40 -05:00
Jenkins
630d44a8cc chore(i18n): update translations 2023-02-12 16:09:38 -05:00
Jenkins
894e16ddf0 chore(i18n): update translations 2023-02-05 16:09:37 -05:00
Muhammad Abdullah Waheed
263c486330 chore: Automate Browserslist DB Update (#987)
* feat: added cron github action to auto update brwoserlist DB periodically

* refactor: used a shared script to update broswerslist DB, create PR and automerge it
2023-01-31 17:41:10 +05:00
Bilal Qamar
b3d33667d4 Updated frontend-build to v12 (#962)
* feat: rebase previous frontend-build upgrade

* chore: make welcome message to default to empty
2023-01-30 12:20:07 -05:00
Jenkins
b500546e8d chore(i18n): update translations 2023-01-29 16:09:37 -05:00
leangseu-edx
cb9e0aa52f feat: copy static to dist folder for prod (#1057)
* feat: copy static to dist folder for prod

* chore: update webpack.prod.config.js

Co-authored-by: Ben Warzeski <bwarzeski@edx.org>

Co-authored-by: Ben Warzeski <bwarzeski@edx.org>
2023-01-25 14:29:00 -05:00
Leangseu Kim
69ff5463b3 fix: update iframe static stylesheet 2023-01-25 10:08:44 -05:00
ihor-romaniuk
3b4561e142 fix: fix alignment in the streak celebration modal 2023-01-24 11:47:43 -05:00
Jenkins
cf3b3a27bc chore(i18n): update translations 2023-01-22 16:09:35 -05:00
Ben Warzeski
3bb7aa06bc Bw/share by unit (#1050)
* fix: disable share feature on component unmount

* fix: share only on set units

* fix: share only on set units

* fix: typo
2023-01-20 11:33:38 -05:00
Ben Warzeski
4cea9e582b Bw/share by unit (#1049)
* fix: disable share feature on component unmount

* fix: share only on set units

* fix: share only on set units
2023-01-20 11:02:37 -05:00
Ben Warzeski
0c74bb5106 fix: disable share feature on component unmount (#1048)
* fix: disable share feature on component unmount

* chore: make sure useEffect run only once

Co-authored-by: Leangseu Kim <lkim@edx.org>
2023-01-20 09:07:18 -05:00
Jansen Kantor
b082f3ed19 feat: remove mmp2p (#1042)
* feat: remove mmp2p experiment folder

* feat: remove AccessExpirationAlertMMP2P

* feat: remove imports references and conditionals for mmp2p
2023-01-19 15:35:01 -05:00
leangseu-edx
5d477cebb2 fix: update social sharing icon and title (#1046)
* fix: update social sharing icon and title

* chore: remove twitter icon
2023-01-18 10:29:09 -05:00
Leangseu Kim
851e49f8fb chore: install react share and implement social share demo 2023-01-17 09:17:40 -05:00
Jansen Kantor
09436dd175 test: each.it indentation (#1043) 2023-01-12 16:00:06 -05:00
Awais Ansari
53c8e01c28 fix: hide discussions sidebar and trigger icon when unit does not exist or enableInContext is false. (#1039)
* fix: removed nonCourseWare topics from topics

* fix: hide discussions sidebar and trigger icon when enableInContext is false
2023-01-09 14:29:42 +05:00
Leangseu Kim
ed2d816bbe fix: upgrade discount space 2023-01-03 12:54:22 -05:00
Awais Ansari
7c067299fb refactor: rename inContext param to inContextSidebar (#1022) 2022-12-30 17:04:33 +05:00
Mehak Nasir
4ee1570bfa fix: sidebar length is fixed (#1023) 2022-12-28 13:34:17 +05:00
Leangseu Kim
91c548847b fix: upgrade paragon to remedy the pipeline fix 2022-12-23 11:15:17 -05:00
renovate[bot]
49440ffb45 chore(deps): update dependency @edx/reactifex to v2.1.1 2022-12-23 11:54:31 +00:00
renovate[bot]
6752447d94 chore(deps): update dependency @edx/browserslist-config to v1.1.1 2022-12-23 05:25:42 +00:00
renovate[bot]
75c6aadb09 fix(deps): update dependency util to v0.12.5 2022-12-23 05:14:37 +00:00
renovate[bot]
9eceb355f6 fix(deps): update dependency reselect to v4.1.7 2022-12-23 05:02:03 +00:00
renovate[bot]
df7786388c fix(deps): update dependency regenerator-runtime to v0.13.11 2022-12-23 04:51:29 +00:00
renovate[bot]
361de31e22 fix(deps): update dependency react-share to v4.4.1 2022-12-23 04:40:17 +00:00
renovate[bot]
9e040ec8f1 fix(deps): update dependency react-redux to v7.2.9 2022-12-22 22:25:46 +00:00
renovate[bot]
8db8aeed71 fix(deps): update dependency classnames to v2.3.2 2022-12-22 22:15:18 +00:00
renovate[bot]
04471e550b fix(deps): update dependency @popperjs/core to v2.11.6 2022-12-22 22:04:21 +00:00
renovate[bot]
925ee97a76 fix(deps): update dependency @edx/frontend-lib-special-exams to v2.1.2 2022-12-22 21:54:00 +00:00
renovate[bot]
65086af173 chore(deps): update dependency @testing-library/jest-dom to v5.16.5 2022-12-22 21:42:53 +00:00
Adam Stankiewicz
33923d9a69 chore: upgrade @edx/frontend-build from v9 -> v12 (#1017)
* chore: ignore eslint issues during frontend-build v9 -> v12 upgrade

* chore: add comment to .eslintrc.js file

* chore: update frontend-build

* chore: update test and remove a few unit tests

Co-authored-by: Leangseu Kim <lkim@edx.org>
2022-12-22 13:44:02 -05:00
leangseu-edx
080d31e934 Revert "fix: update transifex flag for tx cli 1.4.0"
This reverts commit f8a1147571.
2022-12-20 16:06:36 -05:00
Leangseu Kim
f3c80ed39b fix: text in goal modal 2022-12-20 15:03:08 -05:00
Leangseu Kim
1ca4eda08a chore: fix fo format relative time 2022-12-19 11:10:34 -05:00
Jenkins
6193c2d1b3 chore(i18n): update translations 2022-12-18 16:04:31 -05:00
leangseu-edx
f8a1147571 fix: update transifex flag for tx cli 1.4.0 2022-12-14 11:40:24 -05:00
Ghassan Maslamani
edba1600dc fix: fix tabs urls on progress tab
This fix does fixes the url links by getting them from the state
 simliar to how tabs navigation gets them.

 This would allow it work for PUBLIC_PATH is not '/', i.e. in
 tutor.

  This was reported in openedx/build-test-release-wg/issues/222
2022-12-07 17:55:15 +00:00
Jenkins
9a07ad1501 chore(i18n): update translations 2022-12-04 16:04:31 -05:00
Awais Ansari
b343ca7a74 Revert "Revert "fix: remove minHeight from in-context discussion sidebar (#1002)" (#1003)" (#1007)
This reverts commit ba06fd7c98.
2022-12-01 14:17:18 +05:00
Abdullah Waheed
b6d272e99d feat: added new translations in Makefile and updated all the translations 2022-11-30 13:31:18 +00:00
Mubbshar Anwar
0fbb53ae86 fix: update event name (#1004)
update segment event name

VAN-1168
2022-11-23 11:24:20 +05:00
Awais Ansari
ba06fd7c98 Revert "fix: remove minHeight from in-context discussion sidebar (#1002)" (#1003)
This reverts commit 9396fbd9d4.
2022-11-18 18:22:35 +05:00
Awais Ansari
9396fbd9d4 fix: remove minHeight from in-context discussion sidebar (#1002) 2022-11-17 20:59:50 +05:00
Abderraouf Mehdi Bouhali
57d880de70 fix(rtl): mirror new user tour modal background
Mirrors the background image used in the new user tour modal
as it obstructs the readability of the modal title when in RTL
2022-11-07 16:04:17 +00:00
Abderraouf Mehdi Bouhali
bfad5cf684 fix(rtl): use backslash to write fractions (grades) 2022-11-07 16:03:56 +00:00
Jenkins
b0378e1331 chore(i18n): update translations 2022-11-06 16:04:22 -05:00
Andrew Shultz
19d06d60be fix: display onboarding expired after expiration (#997)
Currently expiring soon is displayed 28 days before expiration
and forever afterwards. Adds an actual expired state for after.

Also clarifies the expring soon message which assumed other course,
that was not necessarily true.

Also updates the take action lines when you do not have valid
onboarding to make sure they appear for everything not currently valid
or in process, and updates the submitted process lines to not appear
for expired statuses.
2022-11-03 09:26:42 -04:00
Zachary Hancock
df91fef82e feat: update special exams lib (#992) 2022-10-31 11:49:24 -04:00
Jenkins
7e53ddb685 chore(i18n): update translations 2022-10-30 17:09:24 -04:00
Diana Olarte
be72e36a3a feat: allow runtime configuration (#955)
Allows frontend-app-learning to be configured at
runtime using the LMS's new MFE Configuration API.

Part of openedx/frontend-wg#103
2022-10-27 10:01:43 -04:00
Abderraouf Mehdi Bouhali
fa5cf8f204 fix(rtl): force (%) symbol to follow text direction 2022-10-26 10:31:53 -03:00
Jenkins
759d154e13 chore(i18n): update translations 2022-10-19 05:08:31 -04:00
Abderraouf Mehdi Bouhali
7c4200e9d3 fix(rtl): mirror position of grade rectangles in grade bar (#980)
Translates the rectangles for current and passing
grades when to appear on the right when in RTL.
2022-10-14 12:49:37 -04:00
Kshitij Sobti
e5e73e40ba feat: update discussion sidebar url to allow grouping by subsection (#968)
To enable grouping by subsection in the discussions MFE, this PR updates
the embed URL to the one that supports grouping.

ref: https://github.com/openedx/frontend-app-discussions/pull/281
2022-10-12 17:57:42 +05:00
Muhammad Abdullah Waheed
1892edaade refactor: updated renovate config to auto update minor and patch versions of edx dependencies (#963) 2022-09-28 10:47:31 -04:00
Sarina Canelake
381be9a26b Fix github url strings (org edx -> openedx) (#971)
* fix: fix github url strings (org edx -> openedx)

* fix: update path to .github workflows to read from openedx org
2022-09-14 10:01:48 -04:00
Shafqat Farhan
b3841ef446 feat: [VAN-1081] - Set weekly goal and start course events via query param (#972) 2022-09-13 11:00:09 +05:00
Jenkins
5a897e4ea1 chore(i18n): update translations 2022-09-11 17:07:42 -04:00
Hammad Ahmad Waqas
96ceab8b2f fix: Redirecting to MFE home page instead of access-denied page to save redirection (#970) 2022-09-07 15:39:12 +05:00
Hammad Ahmad Waqas
f9806d0759 feat: added support to check if active enterprise is same as EnterpriseCourseEnrollment object (#967) 2022-09-07 11:39:03 +05:00
edx-semantic-release
a7b584c566 chore(i18n): update translations 2022-08-07 17:03:35 -04:00
Shafqat Farhan
193a184142 feat: [VAN-1000] - Set weekly goal through welcome email via query param (#956) 2022-07-26 09:07:55 +05:00
Hammad Ahmad Waqas
3e76f7ac78 feat: Added support to redirect to DSC if required. (#958) 2022-07-25 13:14:41 +05:00
Maman Khan
36062ff3a6 fix: removed derpreciated codecov package (#953) 2022-06-20 08:10:46 +05:00
ruzniaievdm
6257cb4b58 refactor: Replace PDF course certificate view code (#946)
Co-authored-by: ruzniaievdm <ruzniaievdm@gmail.com>
2022-06-13 09:15:09 -04:00
edx-semantic-release
792d9eb758 chore(i18n): update translations 2022-06-12 17:04:27 -04:00
edx-semantic-release
cd84a15891 chore(i18n): update translations 2022-06-03 13:57:12 -04:00
Diana Catalina Olarte
cafb881a61 fix: show site name instead of edX 2022-05-18 12:23:43 +01:00
edX requirements bot
fd94da0a43 feat: Add package-lock file version check (#941) 2022-05-06 15:53:47 +05:00
edx-semantic-release
1e41547b3e chore(i18n): update translations 2022-04-24 17:04:22 -04:00
Renovate Bot
bf2f123367 fix(deps): update dependency @edx/paragon to v19.18.3 2022-04-21 19:06:04 +00:00
Renovate Bot
0211ecf45e fix(deps): update dependency core-js to v3.22.2 2022-04-21 16:49:25 +00:00
renovate[bot]
36ac129267 fix(deps): update dependency @edx/paragon to v19.18.0 (#926)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-21 12:32:06 -04:00
renovate[bot]
20d4c35d83 fix(deps): update dependency core-js to v3.22.0 (#927)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-21 12:30:10 -04:00
Michael Terry
bbff8e719e fix: remove support for the legacy courseware pages
Access to learners for these pages has been removed, so we don't
need to keep any support for it around. Simplifies some code paths.
2022-04-21 08:50:53 -04:00
edx-semantic-release
5461c08169 chore(i18n): update translations 2022-04-17 17:09:11 -04:00
Michael Terry
ee88a12d8f fix: assume that dates & outline legacy tabs don't exist
They've both been removed from the LMS now. It would be harmless
to keep support for them in place, but it's pointless because
any redirects to the LMS will just come right back to us.

AA-799
2022-04-15 12:26:47 -04:00
Muhammad Adeel Tajamul
9b316bd859 feat: added live tab (#923)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
2022-04-14 10:04:26 +05:00
Renovate Bot
7e7eb83596 fix(deps): update dependency @edx/paragon to v19.15.1 2022-04-13 22:08:11 +00:00
Thomas Tracy
aaa367780d fix: [MICROBA-1769] Cert status before course end (#918)
* fix: [MICROBA-1769] Cert status before course end

Right now, learners who are nonpassing are able to view information
about thier certificates early at the course end screen and progress
pages. This is because we show messaging around the nonpassing state in
some cases before a course ends and certificates are available. This can
also lead to cases where grades are not finalized and students who may
be passing see a scary nonpassing message instead.

This change makes it so during the course exit, a student who finishes a
course before the course is over will see the celebration screen
regardless of passing status. Once the course is over (or if
certificates are available immediately), and they are
still not passing, they will see the nonpassing messaging. The same
change was made for the certificate status alert in the progress tab.
2022-04-13 10:14:00 -04:00
Kshitij Sobti
6d42ee9c6f feat: add discussions tab [BD-38] [TNL-9743] (#879)
* feat: add discussions tab

Adds code to load the discussions MFE in an iframe in the tab so the user isn't redirected to the LMS.

Adds code for the discussions tab, making it dynamically resize based on contents using a postMessage API.

* feat: update path based on user navigation inside discussions MFE

The discussions MFE will send path change events via the postMessage API so that the learning MFE path can be kept in sync. This will allow reloading a page without having the iframe revert to same path each time.
2022-04-13 19:01:29 +05:00
Renovate Bot
41047f4c88 fix(deps): update dependency @edx/paragon to v19.15.0 2022-04-12 09:55:15 -04:00
Renovate Bot
d83551c809 chore(deps): update dependency @testing-library/react to v12.1.5 2022-04-11 23:37:47 +00:00
Renovate Bot
7c3088901d fix(deps): update dependency @edx/frontend-component-footer to v10.2.4 2022-04-11 17:27:50 +00:00
Renovate Bot
518c9ef6c2 chore(deps): update dependency @edx/reactifex to v2 2022-04-11 10:17:28 -04:00
Michael Terry
ae97efaf2b fix: add back es-check & fsevents for now to fix build
A previous commit (7f37575) dropped es-check, which dropped
fsevents, which caused our build system (which is still using
npm@6) to fail with an error like `Unsupported platform for
fsevents` when trying to install fsevents through a dependency
(e.g. when installing npm aliases).

I am reintroducing all the package-lock changes from that commit
to get back fsevents in a state where that error does not occur.

I think a longer-term fix would be to instead upgrade our build
system to node16 / npm6. But this is an easy fix for now to unblock
the builds.
2022-04-11 09:46:10 -04:00
edx-semantic-release
361a099ed1 chore(i18n): update translations 2022-04-10 17:08:59 -04:00
Adam Stankiewicz
7f3757539a build: use shared browserslist config and remove is-es5 check 2022-04-08 16:30:25 -04:00
Michael Terry
44f5132e2a fix: downgrade react and upgrade some other deps to align
Now that we are using node 16, peer dependencies are much more
strict about aligning between all of our dependencies.

This PR downgrades react from 17 to 16 (no changes) and upgrades
paragon and frontend-lib-special-exams to all be on the same
page about what peer dependency ranges are valid.
2022-04-08 16:00:29 -04:00
Renovate Bot
53b19c9be3 chore(deps): update codecov/codecov-action action to v3 2022-04-07 15:24:08 -04:00
edX requirements bot
abc374b60a chore!: Dropped support for Node 12 2022-04-07 15:15:04 -04:00
Muhammad Soban Javed
af837fcac8 fix: run npm i with npm 8 to update lock file version (#915) 2022-04-06 16:41:04 +05:00
Renovate Bot
e328e3d597 chore(deps): update dependency @testing-library/jest-dom to v5.16.4 2022-04-05 18:39:33 +00:00
Renovate Bot
559160213d fix(deps): update dependency @popperjs/core to v2.11.5 2022-04-05 14:53:55 +00:00
Renovate Bot
878a4616f3 fix(deps): update dependency react-redux to v7.2.8 2022-04-04 16:02:20 +00:00
Renovate Bot
3028d79597 fix(deps): update dependency @edx/frontend-component-header to v2.4.6 2022-04-04 15:44:31 +00:00
alangsto
aa0de7663c chore: upgrade special exams lib version (#906) 2022-04-04 06:41:51 -07:00
edx-semantic-release
acd91a1c31 chore(i18n): update translations 2022-04-04 07:10:20 -04:00
Usama Sadiq
b32817b3dd build: update transifex pull translations command (#903) 2022-04-04 16:04:32 +05:00
Thomas Tracy
8b32e5892f chore: [MICROBA-1780] missing copy edit (#902)
We missed a copy edit from the previous PR made for this ticket. Also
changing the id to something more relevant to the message.
2022-04-01 14:28:58 -04:00
Thomas Tracy
76cf85f3d7 chore: [MICROBA-1780] Copy edits (#901)
A partner was not happy with messaging for a course whose
students were in the "earned-not-available" state. This aims to make the
messaging more clear.
2022-04-01 12:43:05 -04:00
Renovate Bot
7d86c501a7 fix(deps): update dependency react-redux to v7.2.7 2022-03-31 22:18:26 +00:00
Renovate Bot
eeee32c100 fix(deps): update dependency @reduxjs/toolkit to v1.8.1 2022-03-31 22:02:34 +00:00
Renovate Bot
95d88a054e fix(deps): update dependency @edx/frontend-platform to v1.15.6 2022-03-31 21:43:23 +00:00
edX requirements bot
550b15a16c fix: transifex migration to new client 2022-03-31 13:33:29 -04:00
Renovate Bot
715393d6ad chore(deps): update dependency @edx/reactifex to v1.1.0 2022-03-31 11:34:28 -04:00
Renovate Bot
19b4241020 fix(deps): update dependency @edx/paragon to v19.13.6 2022-03-31 09:34:02 -04:00
Renovate Bot
09bd5bd748 chore(deps): update dependency ansi-regex to 3.0.1 [security] 2022-03-31 09:32:44 -04:00
Renovate Bot
89771cb56b chore(deps): update dependency @edx/frontend-build to v9.1.4 2022-03-31 02:23:12 +00:00
Michael Terry
3353ee2f9d fix(deps): update dependency @edx/paragon to v19.13.1 2022-03-28 11:30:08 -04:00
Renovate Bot
7c1821382c fix(deps): update dependency @reduxjs/toolkit to v1.8.0 2022-03-28 11:12:22 -04:00
Ghassan Maslamani
b444d677b7 fix: import i18n messages from header for learning to consume
It just makes the learning consume the i18n of the header, as
  otherwise the learning would show the deafult messages of the
  header.
2022-03-28 10:55:44 -04:00
dependabot[bot]
7178f28838 chore(deps): bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 10:44:40 -04:00
Renovate Bot
b07f22193c chore(deps): update dependency ansi-regex to 4.1.1 [security] 2022-03-28 10:43:42 -04:00
Renovate Bot
c6eba42120 chore(deps): update dependency @testing-library/jest-dom to v5.16.3 2022-03-28 09:27:16 -04:00
Michael Terry
7bb2266790 fix: stop stripping most inaccessible sequences from navigation
I had previously made a "fix" to strip all inaccessible sequences
from the learning sequence outline hierarachy, as a way to filter
out unreleased sequences. See commit d1f19a9.

But that was too big a hammer and stripped a lot of released-but-
inaccessible sequences too (e.g. prerequisites).

So now, we adopt a more nuanced approach and explicitly just filter
out sequences that are both inaccessible AND unreleased.

AA-1219
2022-03-28 09:16:05 -04:00
edX Transifex Bot
0a70f9b64e chore(i18n): update translations 2022-03-27 17:08:29 -04:00
Renovate Bot
cfe4432c6b fix(deps): update dependency @edx/frontend-component-footer to v10.2.2 2022-03-24 13:47:25 +00:00
Chris Deery
f7219b4f5d fix: [AA-1219] Crash when locked content rendered without unit ID (#878)
Allows SequenceNavigation to complete rendering even if unitId is not provided.
2022-03-22 17:35:45 -04:00
Renovate Bot
14a19b2794 fix(deps): update dependency @edx/frontend-platform to v1.15.5 2022-03-21 20:21:41 +00:00
edX Transifex Bot
8a9767cdd3 chore(i18n): update translations 2022-03-20 17:08:15 -04:00
Chris Deery
3cba1bbac4 fix: [AA-1207] Remove redundant API fields (#873)
Remove redundant fields from courseware API. These are all found in courseHome: 

- number
- org
- originalUserIsStaff
- isStaff
- verifiedMode
- isMasquerading (virtual field from isStaff and originalUserIsStaff)
2022-03-18 09:20:31 -04:00
Chris Deery
9436770620 fix: improve Guard for Iframe resize (#875)
Fix line that was causing JS errors when it was called in a context without a valid document.
2022-03-17 14:47:19 -04:00
Renovate Bot
d03dd34009 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.18 2022-03-16 20:57:50 +00:00
Renovate Bot
9cdacde4dc chore(deps): update dependency @pact-foundation/pact to v9.17.3 2022-03-16 15:54:09 +00:00
Renovate Bot
a22ac3a776 fix(deps): update dependency @edx/frontend-platform to v1.15.3 2022-03-14 21:29:02 +00:00
Michael Terry
7e19af44da fix: trust the course grade the LMS gives us for progress page
Rather than recompute it ourselves. Now that the backend is fixed
to consider only visible grades for the course grade, we don't need
to try to work around its logic (which is more accurate/consistent
in general).

AA-1217
2022-03-14 13:04:06 -04:00
Dillon Dumesnil
57c3f3080e feat: AA-1205: Enable Entrance Exam support for Learning MFE (#840)
Adds an alert to the courseware if the section is an Entrance Exam. Also
adds a listener to reload the page upon receiving a message from the LMS
indicating the user has now passed the exam.

Commit also contains misc. clean up for i18n messages switching to variable names.
2022-03-14 08:09:15 -07:00
Renovate Bot
385635f5d1 fix(deps): update dependency @popperjs/core to v2.11.4 2022-03-14 11:59:19 +00:00
edX Transifex Bot
a7f763cd2a chore(i18n): update translations 2022-03-13 17:08:02 -04:00
Renovate Bot
c7c9c19771 fix(deps): update dependency @popperjs/core to v2.11.3 2022-03-12 15:29:22 +00:00
julianajlk
1d3a779ef1 feat: Add past expiration messaging for UpgradeNotification (#853)
REV-2500
2022-03-11 10:37:30 -05:00
julianajlk
4f1a50ec24 feat: Add Value Prop past expiration messaging for gated content (#836)
REV-2500
2022-03-11 09:58:44 -05:00
Chris Deery
72d18dc4f9 fix: [AA-1207] unify source of tabs (#861)
Courseware and courseHome both provide tabs to the mfe.
This PR unifies the calls so that tab descriptions are only fetched from courseHome metadata

Remove jest-chain dependencies to make test errors more usable.
2022-03-10 13:29:30 -05:00
Renovate Bot
2197ec0c21 fix(deps): update dependency @edx/paragon to v19.7.0 2022-03-10 09:28:37 -05:00
Renovate Bot
069ac9c234 chore(deps): update dependency @testing-library/react to v12.1.4 2022-03-10 09:00:20 -05:00
Renovate Bot
3edf349969 fix(deps): update dependency @edx/frontend-platform to v1.15.2 2022-03-08 17:41:52 +00:00
Michael Terry
a2516e9fcc fix: avoid a race condition with redux data on course exit page
I would sometimes see a case where we were trying to access
recommendations data before it was defined.
2022-03-07 15:03:03 -05:00
Renovate Bot
554806e9ce chore(deps): update actions/setup-node action to v3 2022-03-07 14:18:38 -05:00
Renovate Bot
ed13128fc4 fix(deps): update dependency prop-types to v15.8.1 2022-03-07 13:40:23 -05:00
Renovate Bot
373a2d88fc fix(deps): update dependency @edx/frontend-component-header to v2.4.5 2022-03-07 13:40:07 -05:00
Renovate Bot
bcd54a4f4b fix(deps): update font awesome 2022-03-07 13:39:44 -05:00
Renovate Bot
c4cb0e5ac2 chore(deps): update actions/checkout action to v3 2022-03-07 13:39:26 -05:00
Renovate Bot
c77d518d04 fix(deps): update dependency core-js to v3.21.1 2022-03-07 13:39:12 -05:00
Renovate Bot
703250c3d2 fix(deps): update dependency @edx/frontend-component-footer to v10.2.1 2022-03-07 16:52:26 +00:00
Michael Terry
35ec314505 chore: drop unused frontend-enterprise-utils dependency 2022-03-07 11:35:50 -05:00
Michael Terry
9fc7951576 fix: downgrade frontend-build to fix our builds
For a still-unknown reason, 9.1.2 breaks our builds in GoCD.
While that investigation continues, let's drop down to a known
working version.
2022-03-07 11:35:50 -05:00
Michael Terry
4ed350c9c6 chore: update testing-library/react to latest 12.x
Also, drop our specific testing-library/dom dependency. We can
get it transitively though testing-library/react, and they are
coupled closely enough that we don't want them out of lockstep
anyway.

Thus, this commit also updates testing-library/dom to 8.x,
because that's what testing-library/react 12.x needs.
2022-03-07 11:35:50 -05:00
Michael Terry
ebed27529c fix: adjust timer test to work again 2022-03-07 11:35:50 -05:00
Michael Terry
24ced5dc63 chore: update testing-library/dom version and clean dev deps
Get on the latest 7.x release and remove some unused dev deps:
- enzyme
- enzyme-adapter-react-17
- glob
2022-03-07 11:35:50 -05:00
Kshitij Sobti
f004d0ab3c feat: Sidebar refactor and add support for discussions sidebar. (#762)
squash!: remove unnecessary styling and migrate to bootstrap and other review feedback
2022-03-07 18:56:05 +05:00
edX Transifex Bot
1bbcc6d052 chore(i18n): update translations 2022-03-06 16:07:49 -05:00
Renovate Bot
3d122e0fb9 chore(deps): update dependency @wojtekmaj/enzyme-adapter-react-17 to v0.6.6 2022-03-04 00:08:49 +00:00
Renovate Bot
685d2d5593 chore(deps): update dependency @pact-foundation/pact to v9.17.2 2022-03-03 21:38:21 +00:00
Michael Terry
97bd45cfa8 chore: update frontend-build, paragon, header, and footer
A collection of edx-owned npm updates. These required an actual
code change of using our own svg rather than directly loading an
svg from paragon, because paragon has its own svgo config that
can potentially conflict with our version of svgo - as it does
when we update frontend-build.

And with the latest versions of frontend-build, we can now use
the latest versions of paragon.

Header and footer updates thrown in for free.
2022-03-03 16:20:05 -05:00
Chris Deery
55dac2696e fix: [AA-1206] resolve access APIs (#838)
* fix: [AA-1206] resolve access APIs

As part of eliminating redundant fields course_access was removed from
 the Courseware metadata API. There are some differences between the
 two APIs - courseware returned an error if the course had flags
 preventing it from being loaded in the MFE, and courseHome had a
 second field, can_load_courseware, that returned a boolean.

 This fix unifies the handling of the access fields to behave consistently.
2022-03-03 16:12:14 -05:00
Michael Terry
4586f8a6ad chore: update frontend-platform to 1.15.1
The bot PRs to do this stalled out, so I'm doing it manually.
2022-03-03 12:38:55 -05:00
Renovate Bot
88bc1f6956 fix(deps): update dependency @popperjs/core to v2.11.2 2022-03-03 12:08:06 -05:00
Jawayria
4f2f17beb3 fix: update workflow 2022-03-03 11:27:11 -05:00
edX requirements bot
8114750796 build: Added support for node v16 2022-03-03 11:27:11 -05:00
Michael Terry
7b945a9fce fix: guard access to a react ref before using it
This was causing some errors ever since we started using the ref
in an event handler - I'm not entirely sure why the ref stops
being valid, unless it's a lifecycle thing. But anyway, this
seems to stop the error in testing.
2022-03-03 11:09:49 -05:00
Michael Terry
48aad3951a fix: resize course handout iframe as contents change
This can happen if a handout has e.g. expanding list elements.

AA-1215
2022-03-01 13:05:35 -05:00
Michael Terry
dcf8da2279 chore: update transifex scripts to api v3
Transifex is sunsetting anything below v3 later this year. Let's
get ahead of our scripts unexpectedly failing.

- Switch 'reactifex' dev dep to our maintained fork at
  '@edx/reactifex' (it has an older released version number,
  but it's still maintained unlike the original)
- Update Makefile commands for new v3 transifex API
2022-02-28 12:49:35 -05:00
Michael Terry
d8e1124a4c chore: update to paragon 19.3.0
No change on our end, this is just to get us as up-to-date as
possible.

This is the latest version of paragon we probably want to use as
long as we still claim to support IE11 via browserlist. We start
getting warnings about grid-auto-rows not being supported in IE11
beginning in paragon 19.4.0 with its new SelectableBox component.

To update our browerlist to drop IE11, we'll need to update our
node version. Which is in progress elsewhere. But for this series
of commits, I've gotten as far as I can / want to with paragon.
2022-02-28 11:19:19 -05:00
Michael Terry
e9f0a658d6 chore: update to paragon 19.1.0
Replace our custom Tour component with the drop-in ProductTour
replacement in paragon.
2022-02-28 11:19:19 -05:00
Michael Terry
7049445969 chore: update to paragon 19.0.0
Adapt to the big <Card> redesign.

Also, as part of 19.0.0, the shadow variables in paragon got
marked as !default, letting the theme override them. This would
normally be fine and good. But the edx.org theme has a dramatically
larger shadow set. And a lot of our cards and card-like components
were designed with a smaller shadow in mind. So I added a new
raised-card custom css class to keep as many cards looking the same.

Notably, I cannot fix the data tables on the Progress tab. So those
might have a larger shadow with this change, but that's unavoidable
and presumably the intent of the edx.org theme authors.
2022-02-28 11:19:19 -05:00
Michael Terry
f17a635e9d chore: update to paragon 17.1.2
Adapt to secret <Spinner> API breakage (they stopped rendering
children, instead requiring a screenReaderText prop).
2022-02-28 11:19:19 -05:00
Michael Terry
cc8ee33dcd chore: update to paragon 17.0.0
- Drop our custom breakpoints (identical to paragon's)
- Drop our custom useWindowSize (and adapt to paragon's version
  not providing a size initially at component mount)
- Drop our dependency on react-responsive
- Drop our dependency on react-break
2022-02-28 11:19:19 -05:00
Ghassan Maslamani
c25ec8f1ae feat: add descriptions for i18n messages (#815)
It fixes: openedx/frontend-wg/issues/74
2022-02-28 09:10:57 -05:00
Sarina Canelake
8325851813 build: add DEPR workflow automation 2022-02-24 16:05:03 -05:00
edX Transifex Bot
a66d2cf524 chore(i18n): update translations 2022-02-20 16:07:24 -05:00
renovate[bot]
628ede3ccc chore(deps): update dependency follow-redirects to 1.14.8 [security] (#800)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-17 16:33:23 -05:00
Michael Terry
c0c51a3028 fix: remove custom newrelic snippet in favor of new frontend-build one (#832) 2022-02-17 16:12:59 -05:00
Simon Chen
947e5e3cb2 fix: deny proctored exam access to audit and honor enrollment tracks (#831)
We need to allow both Timed exams and non-exam types content to be rendered

Co-authored-by: Simon Chen <schen@edX-C02FW0GUML85.local>
2022-02-17 16:10:32 -05:00
renovate[bot]
93baa10141 chore(deps): update dependency node-fetch to 2.6.7 [security] (#821)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-17 15:49:19 -05:00
renovate[bot]
c02bf1eeed chore(deps): update dependency @testing-library/jest-dom to v5.16.2 (#760)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-17 15:47:13 -05:00
renovate[bot]
b4c90ab506 chore(deps): update dependency jest to v27.5.1 (#756)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-17 15:46:24 -05:00
renovate[bot]
e20bed64fb fix(deps): update dependency @edx/paragon to v16.24.0 (#748)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-17 15:45:35 -05:00
Dillon Dumesnil
8285d42b7e fix: AA-1194: Update the breadcrumb link (#829)
Before, for sections, it would link you to the chapter which would
cause a JS error since the sequence endpoint expects sequentials.
This updates to now link to the first subsection within a section when
you hit the section breadcrumb
2022-02-17 12:45:13 -08:00
Simon Chen
74484b7847 fix: revert "fix: deny proctored exam access to audit and honor enrollment tracks (#828)" (#830)
This reverts commit 45d5141769.

Co-authored-by: Simon Chen <schen@edX-C02FW0GUML85.local>
2022-02-17 14:58:21 -05:00
Simon Chen
45d5141769 fix: deny proctored exam access to audit and honor enrollment tracks (#828)
Co-authored-by: Simon Chen <schen@edx-c02fw0guml85.lan>
2022-02-17 14:11:51 -05:00
Michael Terry
3c52eb2e8d feat: stop calling course blocks rest API and assume LS exists (#803)
- Assume that Learning Sequences is available (waffle has been
  removed)
- Stop calling course blocks API, which provided mostly duplicated
  information now.
- Refactor a bit to avoid needing to globally know which units
  exist in sequences. That is now provided just-in-time for only
  the current sequence.
- Add /first and /last URLs that you can use instead of unit IDs
  in URL paths, in service of the above point.

AA-1040
AA-1153
2022-02-17 14:10:24 -05:00
Ghassan Maslamani
616027df86 fix: rewrite calcualtor tips to reflect the supported features (#825)
Fixes #820
  - Removing constants that are no longer supported
  - Removing suffixes that are no longer supported
2022-02-17 11:24:00 -05:00
Kyle McCormick
93790464f8 build: move pact dependency to devDependencies (#823)
Pact is a testing library. It is not used in to run the
Learning MFE application. Therefore, it belongs in the
devDependencies section of package.json, not the
dependencies section.
2022-02-16 09:05:25 -05:00
Chris Deery
c2cb5744a1 fix: [AA-1195] update dependencies for security warnings (#826)
fix: [AA-1195] fix dependency security alerts

Update frontend-build dependency to 9.0.6

Update es-check to 6.2.1
2022-02-15 13:02:19 -05:00
dependabot[bot]
5d62cb2f46 chore(deps): bump nanoid from 3.1.30 to 3.2.0 (#809)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-15 10:43:35 -05:00
dependabot[bot]
0f11fd6245 chore(deps): bump node-fetch from 2.6.1 to 2.6.7 (#817)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-15 10:41:53 -05:00
dependabot[bot]
2d6e4063ed chore(deps): bump follow-redirects from 1.14.1 to 1.14.8 (#824)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-15 10:38:18 -05:00
Bianca Severino
7b6f5ccf86 chore: bump frontend-lib-special-exams to 1.15.3 (#822) 2022-02-14 09:57:08 -05:00
Bianca Severino
61f0ce2023 chore: bump frontend-lib-special-exams to 1.15.2 (#814) 2022-02-08 11:11:02 -05:00
Chris Deery
5706adde4d fix: [AA-1018] api cleanup
revert missing slice from Tabpage call
2022-02-07 11:40:35 -05:00
Chris Deery
ec1c3da725 fix: [AA-1018] api cleanup
Fix error when moving from courseHome to courseware.
2022-02-04 11:48:58 -05:00
Chris Deery
64b0c03d30 fix: [AA-1018] api cleanup
couple of minor fixes from reviews
2022-01-31 11:20:17 -05:00
Chris Deery
3b33aacb3d fix: [AA-1018] api cleanup
Back off change to fetchCourse in courseware/thunks.js
2022-01-31 11:20:17 -05:00
Chris Deery
f907c588c9 fix: [AA-1018] api cleanup
refactor - don't need to synchronize courseHomeMeta and coursewareMeta manually
2022-01-31 11:20:17 -05:00
Chris Deery
99cf1f9f06 fix: [AA-1018] api cleanup
refactor - remove 'slice' prom from TabContainer.jsx
2022-01-31 11:20:17 -05:00
Chris Deery
7f016e55aa fix: [AA-1018] api cleanup
Implement review feedback.
Clean up tests
2022-01-31 11:20:17 -05:00
Chris Deery
f0f8027de4 fix: [AA-1018] api cleanup
Remove extraneous logging statement
2022-01-31 11:20:17 -05:00
Chris Deery
fd3d0f9391 fix: [AA-1018] api cleanup
Remove debugging code
2022-01-31 11:20:17 -05:00
Chris Deery
3fe5bb1733 fix: [AA-1018] api refactor
This is the first step toward clearing out the redundant metadata from the
coursewareMetadata and getting it from a common source - the courseHomeMetadata.

remove username from coursewareMetadata
Remove courseAccess from coursewareMetadata.

Fix all unit tests
Modify classes that use metadataModel to use courseHomeMetadata for common data.
metadataModel still exists as a mechanism to distinguish if a component is under
courseware or courseHome, and it will be renamed or removed in a later refactor.
2022-01-31 11:20:05 -05:00
edX Transifex Bot
6db421eade chore(i18n): update translations 2022-01-23 16:06:37 -05:00
Dillon Dumesnil
b9d1bf0624 feat: AA-1138: Adds in Weekly Goal Celebration Modal (#797)
The logic to show the modal is controlled by the backend.
Displays the modal only in courseware the first time the learner
hits their weekly learning goal. After viewing the goal, the
database row is updated to not show the modal again.

Also updates first section celebration to use the StandardModal
component as the Modal component has been deprecated.
2022-01-18 06:11:36 -08:00
edX Transifex Bot
2789c7415b chore(i18n): update translations 2022-01-16 16:06:24 -05:00
Carla Duarte
8484d98e26 fix: RTL bug on progress tab (#804) 2022-01-14 15:09:11 -05:00
Carla Duarte
b346b741d5 fix: removing overflow-x scroll from checkpoint (#802) 2022-01-14 12:18:36 -05:00
Ihor Romaniuk
eedaa9f2e9 fix: add support for legacy theme static for the LmsHtmlFragment (#785)
Enables an alternative configuration to match the legacy styles. No-op by default.
2022-01-14 11:11:05 -05:00
Zachary Hancock
f2f0cb6008 chore: update special exams lib (#798) 2022-01-13 14:51:51 -05:00
Ihor Romaniuk
b61057f2df feat: add rtl support (#783)
* feat: add rtl support for chart on progress tab

* feat: change 'div' to semantic 'main' tag

* fix: revert changing div to main tag
2022-01-12 09:40:58 -05:00
Carla Duarte
2d46bacdc7 fix: update Tour components and product tour behavior (#794) 2022-01-11 13:50:12 -05:00
Chris Deery
4655b344a7 feat: [AA-922] remove deprecated goals feature (#789)
* fix: [AA-922] remove deprecated goals feature

While the new Weekly Learning Goals were being rolled out, the previous goal setting feature still existed behind a waffle flag.
The Weekly Learning Goals now become the one and only learning goal feature. It is managed behind the course_experience.enable_course_goals flag

- Remove original Goals panel and related components

- Remove references to weeklyLearningGoalEnabled Waffle flag
2022-01-10 13:38:57 -05:00
Michael Terry
41207e953e feat: show error page when xblock fails to render (#795)
AA-1175
2022-01-10 13:33:30 -05:00
Matthew Piatetsky
16a6eeab24 fix: add aria radiogroup role to goals widget (#792) 2022-01-07 14:04:53 -05:00
Michael Terry
907892e7bb fix: don't log errors when we ask for sequence metadata for units (#790) 2022-01-04 15:07:48 -05:00
Zachary Hancock
f5d1b1c897 chore: update special exams library (#788) 2022-01-04 12:32:18 -05:00
edX Transifex Bot
5854afa987 chore(i18n): update translations 2022-01-02 16:10:59 -05:00
edX Transifex Bot
2aa2e42595 chore(i18n): update translations 2021-12-26 16:10:47 -05:00
Chris Deery
edf9e58d6d fix: [AA-1076] show grade override notice (#773)
* fix: [AA-1076] show grade override notice

- Progress page indicates if a grade has been overridden
- add unit test
2021-12-21 14:07:44 -05:00
Michael Terry
d344b501ab fix: add back missing message translation string (#784)
It was accidentally removed when we switched to the external
header (frontend-component-header), but the string is still
actually used.
2021-12-21 13:53:11 -05:00
julianajlk
2bf4f2a0b5 feat: Add NotificationTray persistence by course (#772)
REV-2424
2021-12-21 13:50:07 -05:00
Arslan
de49e8b271 fix: Use Link from router to fix path based routing issue 2021-12-21 12:53:37 +00:00
Michael Terry
fb21f88c02 fix: when LS is enabled, don't re-connect units and sequences (#782)
Learning Sequences (LS) don't need to edit unit blocks at all.
It's not their data and the stitching code didn't have all the
safety guards that the course block normalizer does in api.js.

This fixes an issue with degenerate course layouts (like problems
as direct children of sequences) when LS is enabled. It was trying
to stitch units and sequences together but failing to account for
unitIds that aren't actual units.

Which is technically still supported by the platform, though not
possible in Studio. We could try to do something smarter here, but
that's not LS's job - it should just trust that the unit data is
correctly normalized already. That unit loading code will
eventually move to the sequence metadata anyway (ideally) and LS
won't touch units at all.

AA-1162
2021-12-20 16:55:21 -05:00
Michael Terry
1044d2afc6 feat: use learning sequences even when masquerading (#774)
The backend recently grew support for it, so we can use it
directly instead of falling back to course blocks in that case.

AA-1151
2021-12-20 13:19:38 -05:00
Carla Duarte
aaf2856573 fix: round grades on progress tab (#778) 2021-12-20 09:03:30 -05:00
Asad Iqbal
1546c62e7f feat: Removed course header stuff (#715) 2021-12-20 08:57:05 -05:00
edX Transifex Bot
b8875f3cda chore(i18n): update translations 2021-12-19 16:10:41 -05:00
Carla Duarte
febc0cae0b fix: course breadcrumb styling (#776) 2021-12-16 13:54:32 -05:00
Carla Duarte
cc0c3c24d9 fix: remove launch tour from header (#775) 2021-12-16 12:03:40 -05:00
Carla Duarte
2fa4a837b1 feat: new user course home tour (AA-1027) (#750) 2021-12-14 12:53:10 -05:00
Dillon Dumesnil
32e299e13b feat: AA-1121: Add in eventing for Goals (#770) 2021-12-09 09:11:21 -08:00
Renovate Bot
f92d2e2ecd fix(deps): update dependency @edx/frontend-platform to v1.14.3 2021-12-08 23:28:08 +00:00
Bianca Severino
fffc48b41a chore: update frontend-lib-special-exams to 1.14.1 (#765) 2021-12-08 10:22:10 -05:00
Renovate Bot
0cc2dcdbc5 fix(deps): update dependency @edx/frontend-platform to v1.14.2 2021-12-08 13:09:15 +00:00
Dillon Dumesnil
e9ca92a359 fix: AA-1107: Update styling for goal unsubscribe page (#763) 2021-12-07 11:53:42 -08:00
Renovate Bot
439965847a fix(deps): update dependency core-js to v3.19.3 2021-12-06 09:52:29 +00:00
Simon Chen
436c05487a fix: Only dismiss the modal when masquerading as specific learner (#759)
if the user is masquerading as a specific learner, then dismiss the modal and do not post back and save the Honor Code signature

Co-authored-by: Simon Chen <schen@edX-C02FW0GUML85.local>
2021-12-02 10:08:49 -05:00
Michael Terry
fba300bc5c fix: avoid trying to open a unit as a sequence (#752)
Make sure to always include the sequence ID when changing the URL
from the jump nav dropdown. We got the correct place eventually
anyway, but this avoids some API requests that we know will fail.

AA-1111
2021-12-01 12:28:10 -08:00
julianajlk
8c43de9fc0 feat: Update notification feature to be course specific (#742)
REV-2360
2021-11-30 09:25:14 -05:00
Phillip Shiu
c2b46d50a8 refactor: [REV-2388] remove "non-profit" MM-P2P language (#758) 2021-11-30 09:03:58 -05:00
Renovate Bot
e2ce54dea8 fix(deps): update dependency core-js to v3.19.2 2021-11-29 21:10:12 +00:00
Renovate Bot
af45d899e3 fix(deps): update dependency reselect to v4.1.5 2021-11-25 00:05:33 +00:00
Renovate Bot
15a4ea42b2 chore(deps): update dependency @testing-library/jest-dom to v5.15.1 2021-11-23 16:12:04 +00:00
Renovate Bot
555dddf8de fix(deps): update dependency @edx/frontend-platform to v1.14.1 2021-11-22 13:32:11 +00:00
renovate[bot]
b03e0fd904 chore(deps): update dependency es-check to v6.1.1 (#725)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-19 09:39:52 -05:00
renovate[bot]
a0c2e86a95 fix(deps): update dependency reselect to v4.1.4 (#709)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-19 09:08:15 -05:00
renovate[bot]
39682badef chore(deps): pin dependencies (#735)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-19 09:04:27 -05:00
renovate[bot]
45afc3fbee fix(deps): update dependency @pact-foundation/pact to v9.17.0 (#747)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-19 09:03:56 -05:00
renovate[bot]
15d20dd693 fix(deps): update dependency @edx/paragon to v16.18.0 (#692)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-19 09:03:27 -05:00
renovate[bot]
2d77ad7125 fix(deps): update dependency core-js to v3.19.1 (#705)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:52:43 -05:00
renovate[bot]
33df4d2b7f chore(deps): update dependency @testing-library/user-event to v13.5.0 (#690)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:51:25 -05:00
renovate[bot]
09b16976fd chore(deps): update dependency jest to v27.3.1 (#691)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:50:35 -05:00
renovate[bot]
1b430f99fe chore(deps): update dependency @edx/frontend-build to v8.1.6 (#707)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:49:42 -05:00
renovate[bot]
982f849f41 fix(deps): update dependency @edx/frontend-platform to v1.14.0 (#713)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:46:51 -05:00
renovate[bot]
6ec3a4cb5a chore(deps): update dependency @testing-library/jest-dom to v5.15.0 (#722)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:46:17 -05:00
renovate[bot]
4d29b202b1 chore(deps): update dependency ansi-regex to 5.0.1 [security] (#736)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:45:15 -05:00
renovate[bot]
99ee1da598 chore(deps): update dependency path-parse to 1.0.7 [security] (#737)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:44:53 -05:00
renovate[bot]
b896a64853 chore(deps): update dependency tmpl to 1.0.5 [security] (#738)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-11-18 16:44:10 -05:00
edX Transifex Bot
94ab6d016e chore(i18n): update translations 2021-11-15 02:09:44 +05:00
Michael Terry
41006f5cbf feat: bump New Relic script to v1211 (#743)
From v1209. No notable changes, but just getting up to date.
2021-11-09 13:05:25 -05:00
Chris Deery
9c2c1427e1 fix: [AA-1087] defer goals widget for proctoring (#727)
fix: [AA-1087] defer goals widget for proctoring

- pause showing goals widget while proctor info panel awaits callback.

- Implement tests to check that goals show up when
  proctoring does not

- small refactoring of LearningGoalButton to eliminate test warnings
2021-11-09 10:22:20 -05:00
Bianca Severino
c19f21d257 fix: pass integrity signature flag to exam wrapper (#739) 2021-11-08 13:36:45 -05:00
edX Transifex Bot
1d98de1e0c chore(i18n): update translations 2021-11-08 02:09:30 +05:00
Dillon Dumesnil
64eb268cb0 feat: AA-1020: Add Credit Information to Progress tab (#726)
This is to match what the old Progress tab would show so we
can enable this for all credit courses as well
2021-11-05 11:08:29 -07:00
Carla Duarte
f7428db3c3 feat: product tour components (#695) 2021-11-05 07:57:15 -06:00
Renovate Bot
7986db7027 fix(deps): update dependency @edx/frontend-enterprise-utils to v1.1.1 2021-11-03 09:12:44 +00:00
edX Transifex Bot
7704a8a5d7 chore(i18n): update translations 2021-11-03 00:22:22 +05:00
Phillip Shiu
d88f83311c refactor: [REV-2262] create generic bullets to reuse in all upsell messaging (#723) 2021-11-02 12:42:15 -04:00
Carla Duarte
6f0a69b838 fix: update goal styling (#721) 2021-11-02 09:22:43 -06:00
Chris Deery
7ed1be1960 fix: [AA-1044] add missing h2 for screenreaders (#720)
* fix: [AA-1044] add missing h2 for screenreaders

- Add placeholder h2 tag with message indicating reserve for future use
- internationalize placeholder text
2021-11-01 13:31:19 -04:00
alangsto
663559f8c7 chore: update special exams lib (#718) 2021-11-01 10:24:59 -04:00
edX Transifex Bot
4a56673377 chore(i18n): update translations 2021-11-01 02:09:48 +05:00
Michael Terry
d1f19a9dc4 fix: skip inaccessible learning sequence data (#716)
When normalizing learning sequences, skip inaccessible sequences
and also skip sections with only inaccessible sequences.

This both imitates the legacy course block behavior and also
avoids a failure when merging course block data with LS data
when they disagree about which sequences exist.
2021-10-29 10:53:50 -04:00
Renovate Bot
8a3722a723 fix(deps): update dependency redux to v4.1.2 2021-10-28 10:36:03 +00:00
Chris Deery
4abf6ebdce fix: [AA-1078] weekly learning goals a11y nav (#711)
- Add radio role and aria-checked to button to enable screen reader use
2021-10-27 16:55:41 -04:00
Chris Deery
d1013802ba fix: [AA-1078] a11y and styling for weekly goals (#710)
* fix: [AA-1078] weekly learning goals fine tune

- Fix a11y issues with buttons
- Replace media query with algorithmic approach for determining button layout
- Misc styling fixes
2021-10-27 10:48:14 -04:00
Renovate Bot
581e8c4769 fix(deps): update dependency react-redux to v7.2.6 2021-10-25 21:16:21 +00:00
Michael Terry
ea5c7f516a fix: Revert "fix: disable the call to ecommerce for now (#701)" (#706)
This reverts commit f93519f675.

The original fix may have been working after all and a bogus
test by me using a staff account made me think the discount was
not being offered. Reverting the emergency fix and going to test
the original fix again.
2021-10-25 10:24:23 -04:00
edX Transifex Bot
ce7cef0c6b chore(i18n): update translations 2021-10-25 02:09:35 +05:00
Renovate Bot
45a823e6c7 fix(deps): update dependency @pact-foundation/pact to v9.16.5 2021-10-24 18:08:04 +00:00
David Joy
0b8cf06c29 fix: set modal-lti max-width to be important (#702) 2021-10-21 17:51:27 -04:00
Michael Terry
f93519f675 fix: disable the call to ecommerce for now (#701)
It doesn't seem to be working - instead never calling ecommerce and
always showing the normal non-discount version of the streak modal.

Going to investigate later, but this is just a quick shutoff for
now.
2021-10-21 16:46:10 -04:00
Michael Terry
c39b3ae4c5 fix: don't show 3-day streak discount if it won't provide a discount (#658)
Also, this will pull the actual discount percent from ecommerce,
instead of hardcoding it.

AA-1012
2021-10-21 12:50:14 -04:00
Renovate Bot
c3ea12225d chore(deps): update dependency husky to v7.0.4 2021-10-21 05:26:12 +00:00
Renovate Bot
f914d83510 chore(deps): update dependency @edx/frontend-build to v8.0.6 2021-10-20 16:13:31 +00:00
Matthew Piatetsky
67ea30a45a fix: ensure we don't update goals while users are masquerading (#697) 2021-10-20 10:20:01 -04:00
Phillip Shiu
eabbb440f0 Revert "refactor: create generic bullets to reuse in all upsell messaging (#689)" (#696)
This reverts commit 8735f219e9.
2021-10-20 09:47:39 -04:00
Phillip Shiu
8735f219e9 refactor: create generic bullets to reuse in all upsell messaging (#689)
REV-2262
2021-10-20 07:49:05 -04:00
Chris Deery
c2414ce1ba fix: [AA-906] WeeklyLearningGoals (#694)
Fix incorrect message for returning students
2021-10-19 15:30:04 -04:00
Michael Terry
e6fee7b5b9 fix: catch and redirect URLs with spaces in them (#693)
In an attempt to debug some odd LMS errors (which would happen if
you loaded an MFE page with spaces instead of plus signs), this
commit notices page requests with spaces in the course key and
switches it out for plus signs, logging the incident.
2021-10-19 11:39:06 -04:00
Chris Deery
d8f3c7441e feat: [AA-906] UI for WeeklyLearningGoals (#664)
* feat: [AA-906] UI for WeeklyLearningGoals

Add component to OutlineTab for selecting Weekly Learning Goals
Move start button to before course outline, and put in card with Call to action.
Unit tests
Implement temporary a11y feedback
add react-responsive as a dependency

Everything except for the start/resume button move is behind a waffle flag: course_goals.number_of_days_goals
2021-10-19 10:37:22 -04:00
Renovate Bot
765bf2089c fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.16 2021-10-19 04:14:24 +00:00
Renovate Bot
10fce146fd chore(deps): update dependency @edx/frontend-build to v8.0.5 2021-10-19 04:00:57 +00:00
edX Transifex Bot
b274cb5137 chore(i18n): update translations 2021-10-18 02:10:27 +05:00
Albert (AJ) St. Aubin
a6e539dad2 feat: update the text for unearned certificates
[MICROBA-1536]
2021-10-15 15:38:14 -04:00
renovate[bot]
83fa3f78bc fix(deps): update dependency @edx/paragon to v16.14.9 (#683)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-15 09:08:25 -04:00
renovate[bot]
1e4f3ec151 chore(deps): update dependency @testing-library/user-event to v13.4.1 (#684)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-15 09:08:08 -04:00
renovate[bot]
1ac806b7dd fix(deps): update dependency js-cookie to v3 (#596)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Michael Terry <mterry@edx.org>
2021-10-14 15:57:03 -04:00
renovate[bot]
1d08618be9 fix(deps): update dependency core-js to v3.18.3 (#627)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:40:12 -04:00
renovate[bot]
b90a54759c chore(deps): update dependency jest to v27.2.5 (#619)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:29:41 -04:00
renovate[bot]
a1ef37ca0b fix(deps): update dependency react-router-dom to v5.3.0 (#629)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:27:39 -04:00
renovate[bot]
d178913e4b chore(deps): update dependency glob to v7.2.0 (#649)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:26:30 -04:00
renovate[bot]
9f2ce9d152 chore(deps): update dependency @testing-library/user-event to v13.3.0 (#679)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:25:22 -04:00
renovate[bot]
d6722ca271 fix(deps): update dependency @edx/paragon to v16.14.7 (#644)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:21:27 -04:00
renovate[bot]
aa2004434e fix(deps): update dependency @edx/frontend-enterprise-utils to v1.1.0 (#682)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-10-14 15:18:56 -04:00
Renovate Bot
921f3eef06 fix(deps): update dependency @pact-foundation/pact to v9.16.4 2021-10-11 00:04:09 +00:00
edX Transifex Bot
8337fc79be chore(i18n): update translations 2021-10-11 02:10:22 +05:00
connorhaugh
2230173da8 feat: rollout jump nav to course staff (#672)
As per https://openedx.atlassian.net/browse/TNL-7107 we need to rollout jumpnav to course staff.
2021-10-08 09:33:15 -04:00
Thomas Tracy
09072f318b fix: Fix redirect loop (#676)
MICROBA-1523
2021-10-07 16:35:08 -04:00
Michael Terry
1f7cb2cc28 fix: move NewRelic testing script even higher (#675)
Per their docs, this really should be the first script. And we were
hitting some cloudflare scripts being inserted ahead of us. This
might fix that.
2021-10-07 14:44:32 -04:00
Ned Batchelder
c6ad7c51d3 build: use the organization commitlint check 2021-10-07 13:51:59 -04:00
Andrew Shultz
20d4de09d7 fix: upgrade the frontend-lib-special-exams library to v1.13.3 (#674)
prevents confusing exam errors in non-exam situations

MST-905
2021-10-07 11:35:18 -04:00
Matthew Piatetsky
5aa857f1de fix: Unsubscribe audit users from goal remindners on the course exit page (#671)
This reverts commit 20390d1e33.
2021-10-07 09:14:48 -04:00
Thomas Tracy
3fcc0d87c9 [feat] Add notices redirect to learning MFE (#667)
* [feat] MICROBA-1523 Add notices redirect to learning MFE

To support the notices plugin on platform, this adds a redirect to the
course home page. If a user lands on that page and has not acknowledged
a notice, the user will be redirected to notice instead of the course
home.
2021-10-06 14:25:05 -04:00
Bianca Severino
0bc7faaa56 fix: prevent integrity signature creation while masquerading (#665) 2021-10-06 11:26:04 -04:00
connorhaugh
0889b17e85 fix: course home button error. (#669)
In order to finish off TNL-7107 I needed to meet the acceptance criteria: When learners or educators select a section dropdown item they are taken to the first subsection within that section that is not completed by default. If all subsections are completed they should be taken to the first(subsection) in that section.

This reimagining of Jumpnav does that by lazy loading in the menuItem's destinations and routing the user using React-Router.
2021-10-06 11:14:24 -04:00
connorhaugh
f57f39a787 Revert "Feat lazy load jump nav destinations (#663)" (#670)
This reverts commit dd6a499cfc.
2021-10-05 16:55:58 -04:00
Matthew Piatetsky
20390d1e33 Revert "feat: Unsubscribe audit users from goal remindners on the course exit page (#660)" (#668)
This reverts commit 55b3396acd.
2021-10-05 14:26:05 -04:00
Matthew Piatetsky
55b3396acd feat: Unsubscribe audit users from goal remindners on the course exit page (#660) 2021-10-05 11:18:36 -04:00
Renovate Bot
dae0c8931c fix(deps): update dependency @reduxjs/toolkit to v1.6.2 2021-10-05 03:55:38 +00:00
connorhaugh
dd6a499cfc Feat lazy load jump nav destinations (#663)
In order to finish off TNL-7107 I needed to meet the acceptance criteria: When learners or educators select a section dropdown item they are taken to the first subsection within that section that is not completed by default. If all subsections are completed they should be taken to the first(subsection) in that section.

This reimagining of Jumpnav does that by lazy loading in the menuItem's destinations and routing the user using React-Router.
2021-10-04 08:58:37 -04:00
Renovate Bot
0c006e28de fix(deps): update dependency @pact-foundation/pact to v9.16.3 2021-10-01 12:33:15 +00:00
Renovate Bot
bdba0d2c3c fix(deps): update dependency @pact-foundation/pact to v9.16.2 2021-10-01 02:58:12 +00:00
julianajlk
965a299f6c fix: UpgradeNotification bullet point padding override (#659) 2021-09-29 13:08:53 -04:00
julianajlk
7cfeaab330 fix: Value Prop miscellaneous styling fixes (#657)
REV-2188
2021-09-29 10:15:57 -04:00
edX Transifex Bot
76e3173fc4 chore(i18n): update translations 2021-09-28 21:11:36 +05:00
connorhaugh
e1d3b91dca fix: make breadcrumbs limiting accessible to audit (#655) 2021-09-28 09:23:42 -04:00
julianajlk
0592c40496 Fix lockpaywall container display issue on mobile (#654)
REV-2357
2021-09-27 09:24:49 -04:00
Michael Terry
175a40d9fa fix: correct some escaping on the new relic agent string (#653) 2021-09-24 10:53:02 -04:00
Michael Terry
192a58ab51 fix: hardcode the newrelic agent, to load it earlier (#652)
This is a test, before making a more proper fix in frontend-build.
But I'd like to confirm this fixes some issues we've seen with
newrelic metrics.

AA-1015
2021-09-24 10:09:17 -04:00
Simon Chen
7215db6682 fix: Upgrade the frontend-lib-special-exams library to v1.13.2 (#650)
The special exam timer synced with backend to show accurate count down timer

Co-authored-by: Simon Chen <schen@edx-c02fw0guml85.lan>
2021-09-23 11:13:32 -04:00
Bianca Severino
cb29902152 fix: remove special exam and proctoring flags (#648) 2021-09-22 09:11:55 -04:00
connorhaugh
2932d98976 feat: breadcrumb rolloutout flag + analytics (#647)
As an addendum to https://openedx.atlassian.net/browse/TNL-7107, we want to hide rollout behind a frontend feature flag added in https://github.com/edx/edx-internal/pull/5489. We also want to report these events to the events api with name `edx.ui.lms.jump_nav.selected`. Doummentation to add this event is listed at the following PR: https://github.com/edx/edx-documentation/pull/1982
2021-09-21 15:39:52 -04:00
connorhaugh
8c0e98ad4f feat: Breadcrumb Jump Navigation STAGE ONLY (#641)
Enable faster movement through the course content for learners and course instructors familiar with their course structure using jump navigation selectors in dropdown menus that augment our existing breadcrumbs in the learner sequence experience. When learners/instructors click on sections or subsections these menus are revealed and can be selected to jump to this part of the course.

Implemented using paragon's Selectmenu component, and data from the learning_sequences API.

Note: as the L_S api does not yet have completion data, we are holding off on accepting the completion ACs.

Smoke testing and QA testing will be required, as this feature is prominent in the learner experience. 

The feature is presently only rolled out on stage, but will FF to roll out to instructors on test soon.
2021-09-17 15:39:06 -04:00
Renovate Bot
8b9dfd2f08 fix(deps): update dependency @edx/frontend-platform to v1.12.7 2021-09-17 16:50:13 +00:00
Zainab Amir
e0a81d6cc9 feat: add learner type to course homepage (#643) 2021-09-17 11:19:25 +05:00
Renovate Bot
9cdaa64f64 fix(deps): update dependency @edx/paragon to v16.13.3 2021-09-14 22:30:51 +00:00
Michael Terry
54d96cc162 fix: stop logging course-blocks 403 responses as errors (#637)
They are benign and normal for logged out users. Instead, log them
as info messages, so we can still track them if we need to.

AA-1011
2021-09-13 15:31:43 -04:00
Renovate Bot
3da1fb6581 fix(deps): update dependency @edx/frontend-platform to v1.12.6 2021-09-13 10:43:43 +00:00
edX Transifex Bot
268e8b0b40 chore(i18n): update translations 2021-09-13 02:10:28 +05:00
Renovate Bot
d93cb70966 fix(deps): update dependency @edx/paragon to v16.13.2 2021-09-10 19:16:41 +00:00
Michael Terry
90d6ea8137 feat: notify the user if a sequence is hidden because of due date (#636)
Normally, these sequences are skipped. But if the user manually
goes to the section, they should be notified why they can't access
it. That can easily happen if they bookmarked the page or something.

AA-1000
2021-09-10 11:13:48 -04:00
David Ormsbee
73302d72cb doc: ADR for "Direction of Courseware APIs"
Describing the removal of calls to Course Blocks API for courseware
rendering, how those responsibilities would be split, and the motivation
for doing so. TNL-7326
2021-09-09 12:50:25 -04:00
renovate[bot]
666d9e6b38 fix(deps): update dependency @edx/paragon to v16.13.1 (#620)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-09-09 12:04:53 -04:00
Carla Duarte
fcc0cceb8d fix: update progress grade table styling (AA-934) (#635) 2021-09-09 10:07:35 -04:00
Carla Duarte
597ecb7b4e fix: update SubsectionTitleCell styling (AA-934) (#633) 2021-09-08 10:48:55 -04:00
Renovate Bot
5ec0fec0ff fix(deps): update dependency @pact-foundation/pact to v9.16.1 2021-09-08 13:45:01 +00:00
Renovate Bot
74c75af34d fix(deps): update dependency @edx/frontend-platform to v1.12.5 2021-09-06 11:28:17 +00:00
edX Transifex Bot
9a1966a034 fix(i18n): update translations 2021-09-06 02:05:39 +05:00
Renovate Bot
1868606ee8 fix(deps): update dependency react-redux to v7.2.5 2021-09-04 20:58:27 +00:00
Carla Duarte
70e2aa0203 feat: add page banner to masquerade (AA-877) (#606) 2021-09-03 09:47:39 -04:00
Michael Roytman
bdfbbc0b75 feat: Display "Onboarding Past Due" message in onboarding button if onboarding is past due (#472)
[MST-746](https://openedx.atlassian.net/browse/MST-746)

The ProctoringInfoPanel displays information in a learner's course outline about the state of the learner's onboarding. It displays a link to navigate the learner to the onboarding exam if it is available. If the onboarding exam is not yet released, it displays information about the release date. This code changes adds an "Onboarding Past Due" message to the link if the onboarding is past due, as determined by a call to the LMS onboarding endpoint.
2021-09-01 16:32:00 -04:00
Chris Deery
fda9ab6bce Feat: [AA-950] Streak discount productization (#623)
- Remove Jira tag from StreakCelebrationCouponEnabled prop
- Remove "experiment" from streak discount vars
- Cleaned up warning in unit test
- Added mock function for closeStreakCelebration
- Set End Date to 2 weeks from current date
- Updated unit tests
- Fixed naming issues
- Added official coupon code
- Cast isStreakCelebrationOpen to boolean

Co-authored-by: cdeery <cdeery@edx.edu>
2021-09-01 12:21:43 -04:00
Zachary Hancock
04cc668e9b chore: update special-exams-lib (#625) 2021-08-31 11:09:52 -04:00
alangsto
c91e5d5f58 chore: update frontend-lib-special-exams version (#624) 2021-08-30 16:44:50 -04:00
Renovate Bot
22ca88c981 fix(deps): update dependency core-js to v3.16.4 2021-08-29 17:06:50 +00:00
Renovate Bot
ffd03cb1de fix(deps): update reactrouter monorepo to v5.2.1 2021-08-28 01:50:24 +00:00
edX Transifex Bot
8cfe4bc099 fix(i18n): update translations 2021-08-27 00:49:52 +05:00
Phillip Shiu
191ef9c7b9 fix: add accent to e in resumé (#616)
Fixes: REV-2214
2021-08-26 13:02:05 -04:00
Renovate Bot
ac0813816f fix(deps): update dependency @edx/paragon to v16.9.1 2021-08-26 13:14:15 +00:00
Diane Kaplan
a614145e6d REV-2297: add NotificationTray red dot functionality, so learner notices new prompt 2021-08-25 13:08:23 -04:00
Renovate Bot
ca9a000fd2 chore(deps): update dependency husky to v7.0.2 2021-08-25 03:12:11 +00:00
Renovate Bot
9e2b2ec541 fix(deps): update dependency core-js to v3.16.3 2021-08-24 21:57:17 +00:00
Michael Terry
8552329739 feat: add a new course goal unsubscribe landing page (#612)
URL format: /goal-unsubscribe/<uuid-token>

This is designed to be used in the new course goals feature, where
emails will be sent to learners and those emails will include a
link to this landing page, as an unsubscribe link.

Also, update calls to the LMS course home API to not include the
/v1/ fragment anymore, as we're moving to an unversioned API.

AA-907
2021-08-24 16:10:04 -04:00
Bianca Severino
90fc5f0024 fix: pass originalUserIsStaff to SequenceExamWrapper (#610)
Pass this value to determine whether the user is staff
masquerading as a learner.
2021-08-24 13:01:41 -04:00
Renovate Bot
d3c44f3984 fix(deps): update dependency @edx/frontend-lib-special-exams to v1.12.1 2021-08-24 15:04:16 +00:00
Awais Ansari
a607fe4574 fix: media query max-width effect on course content pages (#600) 2021-08-24 15:08:30 +05:00
Diane Kaplan
2d5e1caae7 feat: cleanup tracking code for obsolete upsell link (#531)
Co-authored-by: Diane Kaplan <dkaplan@edx.org>
See: https://openedx.atlassian.net/browse/REV-2305
2021-08-23 15:26:08 -04:00
Ali Akbar
5087353e88 test: add pact tests for courseware and course-home (#598) 2021-08-23 20:02:31 +05:00
edX Transifex Bot
2171c28825 fix(i18n): update translations 2021-08-23 02:05:31 +05:00
Renovate Bot
adde6e3470 chore(deps): update dependency @edx/frontend-build to v8.0.4 2021-08-20 19:55:35 +00:00
Robert Raposa
0d015be97e build: git ignore local environment overrides (#580)
Git ignore for .env.private to enable local environment
overrides as detailed by frontend-build. See:
https://github.com/edx/frontend-build#override-default-envdevelopment-environment-variables-with-envprivate
2021-08-20 09:13:35 -04:00
Renovate Bot
dfbdcee163 chore(deps): update dependency @edx/frontend-build to v8.0.3 2021-08-19 23:16:57 +00:00
renovate[bot]
3ad7b9e95d fix(deps): update dependency @edx/frontend-platform to v1.12.4 (#605)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Michael Terry <mterry@edx.org>
2021-08-19 11:32:23 -04:00
renovate[bot]
aedee4f847 fix(deps): update dependency @edx/paragon to v16.9.0 (#604)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-19 11:03:10 -04:00
renovate[bot]
6878ef9fe1 fix(deps): update dependency @edx/frontend-enterprise-utils to v1 (#602)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-19 11:02:37 -04:00
Renovate Bot
04dc5a26ec chore(deps): update dependency @edx/frontend-build to v8.0.1 2021-08-18 01:54:46 +00:00
Renovate Bot
33348eabbd fix(deps): update dependency core-js to v3.16.2 2021-08-17 17:10:28 +00:00
edX Transifex Bot
c5a383dfdb fix(i18n): update translations 2021-08-16 02:10:23 +05:00
renovate[bot]
1177c6e2e2 chore(deps): update dependency axios-mock-adapter to v1.20.0 (#597)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-13 08:56:56 -04:00
renovate[bot]
d6fdf1512f chore(deps): update dependency es-check to v6 (#595)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 16:10:22 -04:00
renovate[bot]
936885707d chore(deps): update dependency @testing-library/user-event to v13 (#594)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 16:09:28 -04:00
renovate[bot]
71db431b97 chore(deps): update dependency @edx/frontend-build to v8 (#590)
* chore(deps): update dependency @edx/frontend-build to v8

* fix: install the util package, webpack5 no longer polyfills it

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Michael Terry <mterry@edx.org>
2021-08-12 14:54:55 -04:00
Adam Stankiewicz
b741525bfc fix: replace frontend-enterprise with frontend-enterprise-utils (#593) 2021-08-12 14:42:27 -04:00
renovate[bot]
0394118608 chore(deps): update codecov/codecov-action action to v2 (#589)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 14:14:48 -04:00
renovate[bot]
c6df8cdbb5 fix(deps): update dependency @reduxjs/toolkit to v1.6.1 (#156)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 13:15:21 -04:00
renovate[bot]
1fbd9d4645 fix(deps): update dependency core-js to v3.16.1 (#269)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 13:02:48 -04:00
renovate[bot]
42a3f6b244 fix(deps): update dependency react-helmet to v6.1.0 (#130)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 12:57:40 -04:00
renovate[bot]
6ca4c99c0d fix(deps): update dependency redux to v4.1.1 (#522)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 12:54:56 -04:00
renovate[bot]
5deac01615 fix(deps): pin dependency @pact-foundation/pact to 9.16.0 (#583)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 12:53:01 -04:00
renovate[bot]
1160353ab9 fix(deps): update dependency @edx/paragon to v16.7.0 (#588)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 12:52:43 -04:00
renovate[bot]
ca8cfda9b9 fix(deps): update dependency truncate-html to v1.0.4 (#587)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 12:45:02 -04:00
renovate[bot]
3d9cb20e33 fix(deps): update dependency react-share to v4.4.0 (#216)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 11:52:20 -04:00
renovate[bot]
37f32fddf2 chore(deps): update dependency husky to v7 (#523)
* chore(deps): update dependency husky to v7

* fix: migrate config from husky4 to husky7

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Michael Terry <mterry@edx.org>
2021-08-12 11:52:11 -04:00
Renovate Bot
b5689a7997 fix(deps): update dependency regenerator-runtime to v0.13.9 2021-08-12 15:46:30 +00:00
renovate[bot]
c88ea31c20 fix(deps): update dependency @edx/frontend-component-footer to v10.1.6 (#585)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 11:20:55 -04:00
renovate[bot]
9de77c282d chore(deps): update dependency codecov to v3.8.3 (#584)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 11:20:33 -04:00
renovate[bot]
1b995d2510 fix(deps): update react monorepo to v17 (#255)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 11:20:09 -04:00
renovate[bot]
66300caf30 chore(deps): update dependency @edx/frontend-build to v5.6.14 (#330)
* chore(deps): update dependency @edx/frontend-build to v5.6.14

* fix: avoid circular dependency to keep eslint happy

Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Michael Terry <mterry@edx.org>
2021-08-12 11:20:02 -04:00
Robert Raposa
29391f7741 build: update frontend-platform to 1.12.3 (#582)
* build: update frontend-platform to 1.12.1

* build: update frontend-platform to 1.12.3
2021-08-12 11:03:39 -04:00
renovate[bot]
15782609c3 chore(deps): update dependency @testing-library/user-event to v12.8.3 (#160)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:55:42 -04:00
renovate[bot]
f2fc950678 chore(deps): update actions/setup-node action to v2 (#314)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:49:22 -04:00
renovate[bot]
42445d884f fix(deps): update react monorepo to v16.14.0 (#244)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:28:30 -04:00
renovate[bot]
a5ea7431fc chore(deps): update dependency rosie to v2.1.0 (#454)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:25:19 -04:00
renovate[bot]
df29cd0f9a fix(deps): update font awesome (#154)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:24:40 -04:00
renovate[bot]
4898487a82 chore(deps): update dependency @testing-library/jest-dom to v5.14.1 (#158)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:19:27 -04:00
renovate[bot]
6588153e4c chore(deps): update dependency jest to v27 (#163)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:12:19 -04:00
renovate[bot]
a568c5f2fc chore(deps): update dependency axios-mock-adapter to v1.19.0 (#260)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:06:31 -04:00
renovate[bot]
054afc0475 fix(deps): pin dependency lodash.camelcase to 4.3.0 (#520)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:05:10 -04:00
renovate[bot]
58e8de2c22 chore(deps): update dependency es-check to v5.2.4 (#361)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:03:30 -04:00
renovate[bot]
2b00cecd19 fix(deps): update dependency classnames to v2.3.1 (#518)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-08-12 10:02:31 -04:00
Ali Akbar
58f1634c63 test: add consumer pact tests for courseware (#510) 2021-08-10 14:57:24 +05:00
edX Transifex Bot
2092a5d8d1 fix(i18n): update translations 2021-08-09 02:05:23 +05:00
Michael Terry
1308d1e90b docs: explain the learning MFE specific environment variables (#578)
This adds some links to the docs for general MFE instructions,
plus documents the learning MFE specific ones.

Also fixes the README badge links.
2021-08-06 10:15:06 -04:00
Matt Tuchfarber
3b4dcfefaf fix: ignore grade for allowlist certs (#577)
Allowlist certs were hidden on the progress view because the grade
wasn't passing. This gives "downloadable" certs preference over the
grade.
2021-08-05 16:07:54 -04:00
Zachary Hancock
d4a4cd24ec feat: staff may review exam content without attempt (#576) 2021-08-04 13:50:44 -04:00
Dillon Dumesnil
149ca245fd style: Update subsection title to be greyed out if unavailable (#574)
AA-932
2021-08-03 05:36:32 -07:00
Emma
56ea6d46d4 fix: dfferentiate upsell link eventing between in_course and course_home 2021-08-02 10:40:03 -04:00
Thomas Tracy
d12e93d80a [feat] MB-1192: Add not passing, course ended status to certificate status alert (#548)
[feat] Add not passing, course ended status to certificate status alert on the outline tab
2021-08-02 10:10:08 -04:00
Chris Deery
63c86701de fix: Swap the prev and next icons in rtl (#571)
Right To Left (RTL) languages need to reverse the
direction of the icons in navigation.
This fix corrects the arrows in UnitNavigation.jsx,
which were missed in the previous checkin.

Fixes: AA-891

Co-authored-by: cdeery <cdeery@edx.edu>
2021-08-02 10:01:54 -04:00
edX Transifex Bot
b99910357b fix(i18n): update translations 2021-08-02 02:05:24 +05:00
Michael Terry
b4bedfe3f0 fix: re-enable access error redirects for course home (#570)
These redirects are already in place for the courseware, and will
redirect to the outline page, or the dashboard, or wherever, based
on the type of access error (unenrolled, access expired, survey
needed, etc).

This commit stops the course home tabs from paying attention to the
401 error messages coming from the backend - course_access in the
metadata API handles that now.

This commit also changes our useModel hook to more gracefully handle
not being able to find the model - it is a valid case we want to
allow (still will cause problems if you actually try to use the data,
but will at least provide an object you can inspect).
2021-07-30 14:20:42 -04:00
Brian Mesick
8c41e182a2 feat: Remove upgrade sock from course pages (#556)
REV-2220: The upgrade sock is being removed from the remaining course pages in favor of the new Value Prop work.
2021-07-30 12:55:09 -04:00
David Ormsbee
fae2396977 refactor: Begin transition to Learning Sequences API
For performance and long term simplification reasons, we want to take
the work currently done by the Course Blocks API and split it up between
the Learning Sequences API (course outline) and Sequence Metadata API
(details about the Units in a Sequence). This will also make it easier
to later support different kinds of Sequences, where we might not know
all the details about it at the time we load the course-wide outline
data.

This starts moving over the responsibility for the high level outline
and metadata to Learning Sequences. It requires that the waffle flag
"learning_sequences.use_for_outlines" be active in the LMS. If that flag
is not active, the Learning Sequences API call will return a 403 error,
and this code will fall back to the older behavior.

Some data could not be shifted over yet. Namely:

* Sequence legacy URL is not currently output by the Learning Sequences
  API. This is simple to add, but I don't know if there's any point in
  adding it now that the Courseware MFE is functional for timed exams.
* Unit metadata was not completely shifted over to the Sequence Metadata
  API because doing so would cause blocking requests and would cause a
  noticeable performance regression. This should not be moved over until
  the Sequence Metadata API can be made more performant.
* Effort Estimation currently relies on content introspection of the
  underlying content in a way that the Learning Sequences API does not
  support.

This is the last of a handful of PRs in support of TNL-8330.
2021-07-29 12:04:36 -04:00
Chris Deery
276f2a516a fix: Swap the prev and next icons in rtl (#566)
Right To Left (RTL) languages need to reverse the
direction of the icons in navigation.

Fixes: AA-891

Co-authored-by: cdeery <cdeery@edx.edu>
2021-07-29 11:39:58 -04:00
Michael Terry
01ba277425 fix: adjust syntax of a translation, so transifex will accept it (#568) 2021-07-29 08:56:43 -04:00
Saad Yousaf
64f374855b fix: remove focus state for Navigation tab items. (#567)
Co-authored-by: SaadYousaf <saadyousaf@A006-00314.local>
2021-07-28 21:37:15 +05:00
Carla Duarte
a8348e1568 feat: updating alerts to use Paragon Alert over custom (AA-914) (#557) 2021-07-28 09:39:31 -04:00
Viktor Rusakov
6a3ad1d659 chore: update special exams library version (#564) 2021-07-28 07:22:12 -04:00
Saad Yousaf
2f0933be6e fix: update container-fluid to container-xl class to match legacy experience. (#530)
Co-authored-by: SaadYousaf <saadyousaf@A006-00314.local>
2021-07-28 02:01:26 +05:00
Saad Yousaf
4be3b8a56f fix: update Navigation tab items styling to match design. (#536)
Co-authored-by: SaadYousaf <saadyousaf@A006-00314.local>
2021-07-28 01:28:50 +05:00
Matthew Piatetsky
2075a0b3dd fix: grey out problem scores when a learner does not have access, instead of when a learner does have access (#565) 2021-07-27 11:52:51 -04:00
Michael Terry
52750ef769 fix: temporarily disable course-home denied redirects (#563)
They caused a regression when not logged in or enrolled. Will
hopefully re-enable after developing a fix.
2021-07-27 11:46:38 -04:00
Matthew Piatetsky
e2b00d6684 [AA-933] fix: address issues with progress page fbe exception UI (#561)
* fix: address issues with progress page fbe exception UI

* address comments
2021-07-27 10:59:23 -04:00
Michael Terry
6003865840 fix: remove unused portions of effort estimate experiment (#560)
We are sticking with the sequence version, and abandoning the section
version. This commit also marks the strings for translation, as it
is a real feature now, not just an experiment.

AA-659
2021-07-27 09:46:02 -04:00
Zachary Hancock
30a487ec13 chore: upgrade special-exams library (#562) 2021-07-27 09:26:47 -04:00
Michael Terry
c667e29492 fix: handle course access errors in Course Home side of things too (#558)
The courseware was properly reading the access errors and
redirecting the user as appropriate (like to the dashboard or
whatever).

This requires a backend change to push the error along.
2021-07-26 16:33:59 -04:00
edX Transifex Bot
be4375dd7c fix(i18n): update translations 2021-07-26 02:05:37 +05:00
Dillon Dumesnil
a4d651a77a fix: AA-912: If the URL is not provided, only show display name (#554)
In https://github.com/edx/edx-platform/pull/28233, the logic was updated
to only return a URL if the content was still accessible to the learner.
This handles the case of the URL being null
2021-07-22 07:17:59 -07:00
Diane Kaplan
3703e8d81d feat: add upsell events for progress tab upsell links (#532) 2021-07-22 09:05:16 -04:00
julianajlk
c5e480456a fix: LockPaywall content gating layout issue (#549)
REV-2307
2021-07-21 10:22:05 -04:00
Emma
d57dd66dd2 add edxwelcome code info to unhappy path 2021-07-21 10:19:04 -04:00
Matthew Piatetsky
377f780e85 feat: handle FBE Exception cases on new progress page (#539) 2021-07-20 12:20:53 -04:00
Carla Duarte
057b431818 fix: problem score styling (#546) 2021-07-19 13:47:59 -04:00
edX Transifex Bot
e4a883e335 fix(i18n): update translations 2021-07-19 02:05:25 +05:00
Carla Duarte
984926d97c fix: progress score margin (#545) 2021-07-15 15:28:35 -04:00
Viktor Rusakov
9f81897fd2 feat: update onboarding link on the course home page (#542) 2021-07-15 15:10:17 -04:00
Carla Duarte
270c177a83 feat: add problem scores to progress tab (AA-875) (#538) 2021-07-15 13:47:51 -04:00
Albert (AJ) St. Aubin
915f521976 fix: Corrected status message with incorrect mention of email 2021-07-15 10:27:45 -04:00
Thomas Tracy
903d8d4cfb [feat] MB-1299 Add tracking to cert alert buttons (#541)
* [feat] Add tracking to cert alert buttons
2021-07-14 14:02:53 -04:00
julianajlk
f2f4f5f3a5 Fix bug that was applying the CSS to other active classes (#543) 2021-07-14 12:25:32 -04:00
Diane Kaplan
28d359e715 feat: remove first purchase discount banner from courseware (REV-2132) 2021-07-14 11:44:23 -04:00
julianajlk
d93df0e06f feat: remove value_prop_cookie to show the Notification feature in courseware (#524)
Part 4 of REV-2130
2021-07-14 10:47:32 -04:00
Albert (AJ) St. Aubin
86a4cf9af7 feat: Added the Request Certificate Alert
[MICROBA-678]

When a certificate is in a unexpected state (i.e. notpassing with a
passing grade) this alert will allow the user to attempt to resolve the
issue on their own. It will run the code that checks the certificates
status. It requires that the course is configured to allow users to
Request Certificates though.
2021-07-13 10:52:28 -04:00
Thomas Tracy
e423dddb03 [fix] Add href to dates link on coming soon alert (#535) 2021-07-12 13:33:34 -04:00
Sagirov Evgeniy
f21dad95b5 feat: [BD-26] Timer bar on non-sequence pages (#502)
* feat: Timer bar on non-sequence pages

* chore: Update frontend-lib-special-exams version.

Co-authored-by: Viktor Rusakov <vrusakov66@gmail.com>
Co-authored-by: Igor Degtiarov <igor.degtiarov@raccoongang.com>
2021-07-12 11:12:43 -04:00
Saad Yousaf
9978ddf418 fix: change styling of More button to stay consistent with other navigation items. (#528)
Co-authored-by: SaadYousaf <saadyousaf@A006-00314.local>
2021-07-12 18:15:27 +05:00
edX Transifex Bot
d2a8d870af fix(i18n): update translations 2021-07-12 02:05:26 +05:00
Thomas Tracy
3ef4daecce feat: Add Scheduled content alert
Adds a new alert to the outline page that informs the learner of content
coming soon to the course.
2021-07-08 15:47:12 -04:00
Matthew Piatetsky
d2573a16b1 feat: add user id parameter to progress page (#505) 2021-07-08 12:17:43 -04:00
Albert (AJ) St. Aubin
e7c0ebdfe3 Revert "feat: Add Scheduled content alert"
This reverts commit 83151d291c.
2021-07-07 10:39:15 -04:00
renovate[bot]
1ad2cf73bf fix(deps): update dependency @edx/frontend-lib-special-exams to v1.8.3 (#501)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2021-07-07 09:26:12 -04:00
edX Transifex Bot
d60d782e5a fix(i18n): update translations 2021-07-07 03:11:53 +05:00
Thomas Tracy
83151d291c feat: Add Scheduled content alert
Adds a new alert to the outline page that informs the learner of content
coming soon to the course.
2021-07-06 16:08:55 -04:00
Viktor Rusakov
ed55920f99 refactor: place all logic regarding special exam redirect into single function (#521) 2021-07-06 13:02:29 -04:00
julianajlk
4f1c8a4671 refactor: rename Value Prop related notification components (#516)
Part 3 of REV-2130
2021-07-06 12:43:49 -04:00
edX Transifex Bot
0878bf9f13 fix(i18n): update translations 2021-07-05 02:09:17 +05:00
Zachary Hancock
21f3875dae chore: update special exams lib (#517) 2021-07-02 13:29:30 -04:00
Carla Duarte
d4dc75c5a0 feat: updating shift dates banners (#512) 2021-07-02 10:11:49 -04:00
julianajlk
8a1151e8c5 feat: add Value Prop upgrade notification to Courseware (#511)
Part 2 of REV-2130
2021-07-02 10:11:41 -04:00
julianajlk
5e925c93da Move UpgradeCard to generic directory to be accessible in courseware (#514)
Part 1 of REV-2130, Value Prop work
2021-07-02 09:29:48 -04:00
Jansen Kantor
43033ddc91 move mmp2p useModel call and use outline model (#509) 2021-06-30 09:07:59 -04:00
Kristin Aoki
cf08fa5eb9 fix: Autoscroll moves element to center of page
This PR fixes the anchor tag's position on the page when autoscrolling
is used. Previously, the scroll would move the element to the center of
the page. Now the scroll moves the element to the top of the page. The
only case where the element will not be at the top of the page is when
the element is too close to the bottom of the page and there is not
enough page remaining to force the element to the top.
2021-06-29 09:05:04 -04:00
Thomas Tracy
37ce01c00a Ttracy/microba 1192 early with info display behavior (#497)
* [feat] Add ID verification Alert to course home

if a user has a verified seat, but is in the unverified certificate
status state, the certificateStatusAlert will now show a message letting
the learner know they need to verify in order to earn a certificate.

This does not remove the message about the verification deadline in the
right sidebar of the course home.
2021-06-28 12:20:06 -04:00
David Ormsbee
9edac2519a fix: remove sequences we shouldn't see by using learning_sequences
Removes sequences we shouldn't see by using the Learning Sequences API
(TNL-8377). Depends on https://github.com/edx/edx-platform/pull/27955

It works by adding a call to the Learning Sequences API and (if that
endpoint is enabled, i.e. returns 200 for this user+course), uses the
results of that endpoint to remove sequences from the Course Blocks API
call. Learning Sequences knows how to do things like bubble up the
content group settings of units to sequences for the case where all
units have the same restrictions and the user would see an empty
sequence.
2021-06-28 11:41:56 -04:00
Bianca Severino
f9fbc1eb49 fix: add missing check for graded units (#508)
This fixes a bug where if the learner needs an integrity signature, but
the unit is not graded, neither the honor code panel nor the unit
content would display.
2021-06-28 10:12:58 -04:00
edX Transifex Bot
5c68c1d554 fix(i18n): update translations 2021-06-28 02:09:03 +05:00
Zachary Hancock
4180a2e7a0 chore: update special exams library (#503) 2021-06-24 13:10:51 -04:00
Brian Mesick
554e63d653 feat: Remove upsell banner on course home (#499)
REV-2233: The upsell banner now duplicates the sidebar banner. Removing it in favor of the new implementation on course home, but keeping the masquerade message that shows instructors when a learner lost access to the course.
2021-06-24 12:46:57 -04:00
Kristin Aoki
c9f299eada fix: Autoscroll on page when using jump_to_id
This PR adds a URL hash check to useEffect. Previously the anchor tags
that use jump_to_id would remain at the top of the page. As a result,
users would have to manually scroll to the target location or just read
the full page. Now when the page has a URL hash, it will send the hash
to the listener in the iframe. Using the message listener, it receives
an object with offset in the event.data and the page will scroll to the
location provided by offset. This change will impact the Learner in the
New Experience view.
2021-06-23 12:57:47 -04:00
Carla Duarte
d1bb46eef3 AA-815: ui and a11y progress tab fixes (#494) 2021-06-23 09:42:12 -04:00
Renovate Bot
492c573f56 fix(deps): update dependency @edx/frontend-component-footer to v10.1.5 2021-06-23 08:03:06 +00:00
Brian Mesick
0c55863a3d Revert "feat: Remove upsell banner on course home (#493)" (#498)
This reverts commit fbd9d858e4.
2021-06-22 10:37:11 -04:00
Brian Mesick
fbd9d858e4 feat: Remove upsell banner on course home (#493)
REV-2233: The upsell banner now duplicates the sidebar banner. Removing it in favor of the new implementation on course home, but keeping the masquerade message that shows instructors when a learner lost access to the course.
2021-06-22 10:01:57 -04:00
Rebecca Graber
7b0429f472 feat: make course recommendations a part of the course celebration (#486) 2021-06-21 13:18:35 -04:00
Brian Mesick
56decd8ed0 feat: Remove course sock from OutlineTab. (#495)
REV-2122: As part of the Value Prop implementation, we are removing the course sock from Course Home
2021-06-21 12:55:11 -04:00
Matthew Piatetsky
3155055276 feat: change color of start/resume course button (#496) 2021-06-21 10:58:03 -04:00
Albert (AJ) St. Aubin
1b84930a84 feature: Added notification and link to get to a users PDF cert.
This feature will allow users with downloadable PDF certificates to see
the certificate status alert and then access their certificate on the
Course Outline page. This should only show once a learner has earned a
certificate and that certificate is available.
2021-06-17 07:33:33 -04:00
Michael Terry
99185a9b8a fix: guard against courseGoals being undefined (#489)
No idea why this would happen honestly - it looks always defined
from the backend, and api.js doesn't transorm it. But we are seeing
JS errors related to it. So trying this as a first pass.

AA-848
2021-06-15 13:13:38 -04:00
Carla Duarte
e9c3a6bc5e fix: update assignment policy logic (#483) 2021-06-14 11:22:57 -04:00
Sagirov Evgeniy
5a30cddd32 feat: Added 'allow_proctoring_opt_out' attribute for SequenceMetadata API. (#485) 2021-06-14 10:44:37 -04:00
Ihor Romaniuk
432cb669f5 feat: add temporary flag for enabling/disabling proctored exams (#464) 2021-06-14 10:42:53 -04:00
Matthew Piatetsky
26e1eb64c5 fix: ensure clicking the bookmarks button doesn't break the unitHasLoaded property (#481) 2021-06-14 09:36:15 -04:00
edX Transifex Bot
30e0e3b8f4 fix(i18n): update translations 2021-06-14 02:08:38 +05:00
Carla Duarte
46e459aaf3 fix: update progress tab assignment policy logic (#482) 2021-06-11 12:05:40 -04:00
Carla Duarte
6949fa8201 fix: updating progress tab to better respect showGrades field (#480) 2021-06-11 09:42:06 -04:00
Albert (AJ) St. Aubin
fab2da4586 feature: Improve the Certificate Alerts in the outline to support new
statuses

[MICROBA-678]

These changes refactor the CertificateAvailableAlert and add new
features to it to support more status alerts for certificates. It
attempts to do so in an iterative manner so that new/updated alerts can
be included over time.
2021-06-10 13:03:12 -04:00
Kristin Aoki
6a402c50ea fix: Scroll page when html anchor tag clicked
This PR adds a listener check to messages. Previously the anchor tags that were set to scroll on the page to another element would open the link outside of the iframe and redirect the parent page. As a result, users would have to have to click the back arrow to navigate back to the course and continue the unit. Now when the listener receives an object with offset in the event.data, the page will scroll to the location provided by offset. The offset is only received when a user clicks on an anchor tag in the unit iframe that focuses on another element on the page. This change will impact the Learner.

Jira issue: TNL-8312
2021-06-09 13:18:46 -04:00
Zachary Hancock
bfeb8c70c0 fix: pin special exams library (#479) 2021-06-09 11:09:05 -04:00
David Ormsbee
cf61c7a747 fix: display Unit titles as <h1> (TNL-8387)
Unit titles were being written to the page as <h2> because the old
courseware experience reserved <h1> for wrapping the header logo link.
We've since determined that this is not a best practice, and the new
courseware MFE in this repo no longer uses a <h1> for that purpose, but
the Unit title was never promoted from <h2> to <h1> until this commit.

Course teams have traditionally been permitted to use <h3>-<h6> in their
content. Making this change does mean that there will now be a gap with
some content, where we skip from <h1> to <h3>. For the short term, we
are NOT recommending course teams use <h2>, until we have a better
chance to evaluate whether that heading should remain reserved for
platform-level use.
2021-06-07 13:49:58 -04:00
Adeel Ehsan
a003059c8f Account activation pop up added: (#474)
VAN-435
2021-06-07 10:33:59 +05:00
Adeel Ehsan
854010ba52 Revert "Account activation pop up added: (#425)" (#473)
This reverts commit 07b82b1d87.
2021-06-05 01:14:52 +05:00
Adeel Ehsan
07b82b1d87 Account activation pop up added: (#425)
VAN-435
2021-06-04 20:13:12 +05:00
Bianca Severino
5c204ad0f9 feat: add honor code component (#465)
This component blocks access to graded units when
the user is required to sign the integrity agreement for
the course. Once signed, it will not appear for the course
again.
2021-06-04 09:06:32 -04:00
Matthew Piatetsky
5bfca28450 feat: update streak discount coupon expiration date (#471) 2021-06-02 17:12:56 -04:00
Carla Duarte
a36da4cd84 AA-807: progress tab eventing (#470) 2021-06-02 14:31:13 -04:00
Diane Kaplan
f08a23ecf9 feat: remove first purchase discount banner from course home (REV-2253) 2021-06-01 09:12:28 -04:00
edX Transifex Bot
3432b0c73b fix(i18n): update translations 2021-05-29 02:00:46 +05:00
edX Transifex Bot
c1c3d5c68f fix(i18n): update translations 2021-05-29 00:03:12 +05:00
julianajlk
2a52534442 fix: gated content banner discount display (#466)
REV-2232
2021-05-28 13:38:49 -04:00
Carla Duarte
519cf27c4e AA-813: fix progress tab related links (#461) 2021-05-26 12:01:18 -04:00
Carla Duarte
9d07f26f13 AA-807: upgrade deadline passed on progress tab (#463) 2021-05-26 12:01:12 -04:00
Albert (AJ) St. Aubin
fdfb60bee8 feat: Updating the messages for certificate availability.
[MICROBA-678]
2021-05-26 07:23:48 -04:00
Dillon Dumesnil
75c9e93241 refactor: Update the defaults for our .env files (#459)
Nulls can provide undesired behavior so we want to switch to empty strings
2021-05-24 09:08:48 -07:00
Vladas Tamoshaitis
a5ba5655b6 feet: [BD-26] Add support for special exams (#435)
* feat: add packages dir to .gitignore

* Investigate exam redirect (#2)

* feat: remove exam redirect

* feat: take control over exam instructions

* refactor: use fedx code structure

* fix: remove debug logging, remove redirect check

Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>

* Add state and reducer for check microfrontend_special_exams waffle flag (#4)

* feat: add state and reducer for check microfrontend_special_exams waffle flag

* fix: rename special exams enabled flag

* fix: rename reducer for setting special exams enabled flag

* refactor: timer feature

* feat(tests): extend tests + fix failing ones, fix quality

* fix: revert removing package lock file

Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>

* fix: naming of waffle flag helpers to reflect relation with mfe

* fix: change naming of the waffle flag

* fix: revert remove package lock file

* feat: switch to @edx npm package

* fix: Remove redundant references from .gitignore

* fix: add is_mfe_special_exams_enabled to courseMetadata.factory.js

* fix: fix tests for 'Sequence' content wrapped in 'SequenceExamWrapper'

Co-authored-by: Sagirov Eugeniy <sagirov19@gmail.com>
Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>
Co-authored-by: Sagirov Evgeniy <34642612+UvgenGen@users.noreply.github.com>
Co-authored-by: Igor Degtiarov <igor.degtiarov@raccoongang.com>
2021-05-24 08:44:01 -04:00
edX Transifex Bot
46056a0c53 fix(i18n): update translations 2021-05-24 02:08:01 +05:00
Carla Duarte
05b2439ff6 AA-816: update grade summary calculations (#456) 2021-05-19 15:44:49 -04:00
Carla Duarte
663bf7562b AA-814: course grade footer fix (#455) 2021-05-19 13:44:28 -04:00
Renovate Bot
58e29d81be fix(deps): update dependency react-redux to v7.2.4 2021-05-17 20:40:00 +00:00
Renovate Bot
027eeb8a49 chore(deps): update dependency glob to v7.1.7 2021-05-17 20:05:07 +00:00
Renovate Bot
e6a4bcd833 chore(deps): update dependency codecov to v3.8.2 2021-05-17 19:09:59 +00:00
Julia Eskew
1da461e2de feat: Support classifying certain errors as ignored errors in an MFE. (#447)
Ignored errors are sent to New Relic as page actions instead of JS errors,
allowing those errors to still be tracked as occurring but without causing
unnecessary alerts.

Ignored errors are configured per-MFE, *not* globally.

Bump the frontend-platform version to 1.10.2.

Add IGNORED_ERROR_REGEX variable for use in development. The actual
production value will be read from the YAML in edx-internal.

TNL-7924
2021-05-17 13:43:01 -04:00
Matthew Piatetsky
3c07cab8c2 [AA-746] fix: add hour/minute to assignment due dates and update dates timeline with branding styles (#450)
* fix: add hour/minute to assignment due dates and update dates timeline with branding styles

* update badge styles
2021-05-17 12:39:34 -04:00
Carla Duarte
110088688a fix: grade range bug (#449) 2021-05-17 09:23:49 -04:00
Matthew Piatetsky
d8243d6ea8 fix: remove currency symbol to avoid double currency symbol with some upgrade buttons (#448) 2021-05-14 09:48:50 -04:00
Carla Duarte
b1fdbcccf3 AA-790: progress tab handle unenrolled/unauthenticated users (#445) 2021-05-13 15:07:19 -04:00
edX Transifex Bot
00205d4b1f fix(i18n): update translations 2021-05-12 22:29:14 +05:00
julianajlk
6100f3ac2e fix: i18n translations for UpgradeCard (#442)
REV-2126 Value Prop
2021-05-12 09:50:08 -04:00
Matthew Piatetsky
6fa6de4543 fix: don't throw an error when there is no verified mode (#444)
AA-759
2021-05-11 16:16:41 -04:00
Matthew Piatetsky
d0bcb19754 feat: Set up streak celebration discount experiment (#431)
As part of this work, the streak celebration has been migrated from a Paragon Modal to a Modal Dialog
AA-759
2021-05-11 14:06:03 -04:00
Carla Duarte
0f69ed5502 AA-792: Progress tab UI fixes (#443) 2021-05-10 16:43:38 -04:00
edX Transifex Bot
039d761a27 fix(i18n): update translations 2021-05-10 02:07:36 +05:00
Diane Kaplan
e2dd081d44 fix checkmark bullets to vertically align with text (#440)
Co-authored-by: Diane Kaplan <dkaplan@edx.org>
2021-05-07 12:37:38 -04:00
Emma Green
7e75671618 add important so that it overrides other style 2021-05-07 10:48:23 -04:00
Emma Green
5ca10042c8 fix checkmark bullets to line up with text 2021-05-07 10:09:57 -04:00
alangsto
64979ecaf0 fix: Remove confusing language on proctoring info panel (#437)
MST-700. Remove language that says learners with a verified onboarding can take exams. This created confusion for learners who did not realize they must also complete IDV before taking a proctored exam.
2021-05-06 15:33:55 -04:00
Ben Holt
0175c4cf27 Guarded for undefined headers in getOutlineTabData 2021-05-06 12:32:16 -04:00
Emma Green
531f6d96ae more stuffs 2021-05-06 12:32:16 -04:00
Emma Green
997be712f1 add time offset 2021-05-06 12:32:16 -04:00
Emma Green
7e5dacf68d static expiration box 2021-05-06 12:32:16 -04:00
alangsto
73fa56d401 fix: update border color for approved in another course status (#436) 2021-05-06 10:25:56 -04:00
Michael Terry
e46977f50d fix: use new xblock handler URLs for sequence blocks (#423)
They've changed to proper new-style handlers, so the URL also
changed. This will let us get the fix for green checkmarks showing
up as audit users pass FBE units, even though those units aren't
actually complete.

AA-409
2021-05-06 09:38:57 -04:00
Awais Jibran
28e1f6f65a Fix height issue in safari (#434) 2021-05-05 23:10:42 +05:00
Julia Eskew
6c257271bb feat: Deny course staff visibility of legacy courseware. (#427)
Currently, course staff can always view their courses
in the Legacy courseware experience.

With this change, course staff will *not* be able
to view their courses if the New (MFE) courseware
experience has been enabled for them.

This does not affect global staff, and it does not
affect courses that are still running in the Legacy
experience.

Adds a new parameter returned by the course metadata API
used by the courseware MFE to determine if the button to
show legacy experience should be displayed or not.

TNL-8203
2021-05-04 13:57:00 -04:00
Carla Duarte
36f567c834 fix: accurately represent pass/fail grade range (#433) 2021-05-04 11:51:44 -04:00
Carla Duarte
c6627a0854 fix: remove sr-only scss mixin from unit nav buttons (#432) 2021-05-04 09:16:49 -04:00
Carla Duarte
608db6d423 AA-723: scss fixes (#430) 2021-05-03 14:25:56 -04:00
Carla Duarte
72168b56f8 AA-723: progress tab locked content experience (#426) 2021-05-03 13:11:22 -04:00
edX Transifex Bot
5af20067b8 fix(i18n): update translations 2021-05-03 20:05:51 +05:00
edX Transifex Bot
c8ae544c8b fix(i18n): update translations 2021-05-03 02:07:36 +05:00
julianajlk
28fddc5550 feat: Add Sidebar and SidebarNotificationButton components for Value Prop (#414)
REV-2125
2021-04-29 11:36:55 -04:00
Matthew Piatetsky
ef635b2a9b feat: Add certificate status component to the new progress page (#415)
Much of the logic is copied from the course exit certificate states.
AA-719
2021-04-29 09:39:07 -04:00
Matthew Piatetsky
ce69d57dc8 fix: add back events for course home and courseware sock upgrade button (#424)
AA-728
2021-04-28 12:20:08 -04:00
Dillon Dumesnil
43aa6291c3 fix: AA-663: Update header text for CourseCompletion (#421)
If the marketing url is not set, we shouldn't have a message
about sharing.
2021-04-26 07:53:54 -07:00
Carla Duarte
e9f63674ca AA-721: Course grade bar (#413) 2021-04-26 10:05:14 -04:00
Saad Yousaf
41b97ba638 fix: center content by fixing the container-fluid class (#419)
Co-authored-by: SaadYousaf <saadyousaf@A006-00314.local>
2021-04-24 02:36:47 +05:00
Dillon Dumesnil
8a7c61b64a feat: Switch to default values for 12 vs 24 hour time. (#420)
Our current version on react-intl doesn't support hourCycle
anyway and after speaking to product, we feel comfortable with
letting it default based on locale.
2021-04-22 12:04:13 -07:00
Dillon Dumesnil
34dbcb7ea6 fix: AA-738: Switch our use of FormattedTime to use hourCycle (#418)
We had a bug reported where learners were seeing a due date like
March 24, 24:59 instead of March 25, 00:59. This is a bug that only
shows up in Chrome. The hour12 flag overrides the hourCycle flag so
we are just going to swap the two. h23 means a 24 hour format ranging
from 0-23 (there also exists a h24 option which goes from 1-24).

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
for any additional details on the options.
2021-04-21 12:15:13 -07:00
Kyle McCormick
ca8122686b fix: allow media access through unit iframe (#412)
Set the `allow` attribute of the unit iframe to allow
access to camera, MIDI, location, and encrpyted media.

Access to these features was implicitly allowed in older
browser versions. However, in the current versions of
at least Chromium and Firefox, iframed content must be
explicitly granted the ability to request media access.

This fixes a bug where content requiring microphone
access did not work in the Learning MFE.

TNL-7675
2021-04-21 14:30:02 -04:00
edX Transifex Bot
4472541008 fix(i18n): update translations 2021-04-16 10:18:04 -04:00
Diane Kaplan
e5c8dad319 [REV-2127] feat: update gated content lock screen to Value Prop designs (#394) 2021-04-16 08:54:03 -04:00
stvn
3e82152ae7 Merge PR #233 renovate/codecov-3.x
* Commits:
  chore(deps): update dependency codecov to v3.8.1
2021-04-14 08:21:38 -07:00
Renovate Bot
4d2bd81bf0 chore(deps): update dependency codecov to v3.8.1 2021-04-13 15:06:26 +00:00
Carla Duarte
aca45fb26e AA-720: Progress Tab Course Completion chart (#407) 2021-04-13 10:08:13 -04:00
edX Transifex Bot
d13bb04648 fix(i18n): update translations 2021-04-11 17:06:50 -04:00
Bianca Severino
8dc7593780 fix: pass username into proctoring info panel (#406)
Pass a username into the proctoring info panel, allowing staff
to view a specific learner's onboarding status while masquerading.
2021-04-09 12:55:48 -04:00
Kyle McCormick
88005ea5d2 refactor!: clean up unused references to lms_web_url (#409)
Before edx-platform version 19ba691,
only `lms_web_url` was exposed from the course
blocks API. Now that the API also exposes
`legacy_web_url`, we can stop falling back
to `lms_web_url` when `legacy_web_url` is
absent.

TNL-7796
2021-04-09 10:36:04 -04:00
Emma Green
e86f4a88cc small changes to the way the price is displayed on the course home page to pave the way for the expiration box 2021-04-07 12:02:48 -04:00
Kyle McCormick
cf58ff3d3f feat: Use legacy_web_url to redirect to legacy courseware (#404)
As part of making the new courseware experience the
default for staff, the LMS /jump_to/ links that are
exposed by the Course Blocks API via the `lms_web_url`
field will soon direct users to whichever experience
is active to them (instead of always directing to
the legacy experience & relying on the learner
redirect).

Because of this, the MFE can no longer rely on
`lms_web_url` to land a staff user to the legacy
experience. However, the aformentioned change
will also introduce a `legacy_web_url` field
to the API, which we *can* use for this purpose.

TNL-7796
2021-04-07 09:21:07 -04:00
edX Transifex Bot
32ac3632d0 fix(i18n): update translations 2021-04-04 17:06:51 -04:00
Diane Kaplan
6abf8531bb feat: ahead of value prop designs, update text to prep translations (REV-2127)
Co-authored-by: Diane Kaplan <dkaplan@edx.org>
2021-04-01 10:35:19 -04:00
Kyle McCormick
353964e75c feat: handle courseware paths more liberally (#395)
Valid courseware URLs currently include:
* /course/:courseId
* /course/:courseId/:sequenceId
* /course/:courseId/:sequenceId/:unitId

In this commit we add support for:
* /course/:courseId/:sectionId
* /course/:courseId/:sectionId/:unitId
* /course/:courseId/:unitId

All URL forms still redirect to:
  /course/:courseId/:sequenceId/:unitId

See ADR #8 for more context.

All changes:
* refactor: allow courseBlocks factory to build multiple sections
* refactor: make CoursewareContainer tests less brittle & stateful
* feat: handle courseware paths more liberally
* refactor: reorder, rename, & comment redirection functions

TNL-7796
2021-04-01 09:10:00 -04:00
Carla Duarte
6a376b20c7 AA-722: Progress Tab (#391) 2021-03-31 15:00:14 -04:00
Ben Warzeski
162f0ceeb5 Mmp2p 3 (#402)
* [MM-P2P] move access and meta logic to initialization and store in state

* move model access to thunk action
2021-03-31 10:43:38 -04:00
Ben Warzeski
9dcb91af9e mmp2p: handle missing verified-mode info after upgrade deadline (#401) 2021-03-30 11:18:49 -04:00
Kyle McCormick
4d1ed0f357 refactor: de-dupe factories between courseware and course-home (#400) 2021-03-29 16:05:51 -04:00
edX Transifex Bot
d94c7ad003 fix(i18n): update translations 2021-03-28 17:06:42 -04:00
Zachary Hancock
edef36becb update other course approved messaging (#398) 2021-03-26 13:33:37 -04:00
Matthew Piatetsky
29a13f729a fix: remove extra x in first section celebration modal (#397)
x icon that was added to paragon as part of the streak celebration modal is redundant with the x added to only the first section celebration modal
AA-713
2021-03-22 10:00:27 -04:00
edX Transifex Bot
6a6bddc5c8 fix(i18n): update translations 2021-03-21 17:06:30 -04:00
Carla Duarte
b12f184d18 AA-712: upsell link click tracking (#393)
* AA-712: course_home_audit_access_expires and in_course_audit_access_expires

* AA-712: course_home_welcome and in_course_welcome

* AA-712: course_home_dates

* AA-712: course_home_course_tools

* AA-712: course_home_upgrade_shift_dates and dates_upgrade

* AA-712: fixing up PR comments
2021-03-19 12:36:36 -04:00
Carla Duarte
45a68973b7 AA-711: implement upsell click events (#392) 2021-03-16 16:10:47 -04:00
Ben Holt
ab98cca421 Cleaned up all references to REV-1512's value prop experiment (#379)
Cleaned up all references to REV-1512's value prop experiment [REV-2123]
2021-03-15 14:31:21 -04:00
edX Transifex Bot
413b189293 fix(i18n): update translations 2021-03-14 17:06:28 -04:00
Matthew Carter
774b7bb1fc Set flyover visability default to true (#388) 2021-03-12 14:44:38 -05:00
Matthew Carter
ff93d7f4d4 Update language for last date to upgrade (#386) 2021-03-12 10:01:45 -05:00
Carla Duarte
e3d9ff9ed3 AA-213: Redirect progress tab to legacy (#384) 2021-03-11 15:46:37 -05:00
Ben Warzeski
6e2294e279 MMP2P G2 Activation (2nd try) (#382)
* upgrade paragon

* mmp2p experiment code

* mmp2p courseware triggers

* mmp2p course-home triggers

* mmp2p load styles

* mmp2p - add missed locator docstrings

* mmp2p test fixes

* add lazy loading for image-bearing components

* mmp2p experiment README

* mmp2p add lazy loading for sidecard

* generalize prices for currency options

* mmp2p fix flyover mobile args

* mmp2p fix lock paywall border display

* mmp2 - add safety-rail around verifiedmode access
2021-03-11 11:13:38 -05:00
Rebecca Graber
c17beeb908 WS-1740 logging, copy, style changes (#383) 2021-03-11 09:39:05 -05:00
Ben Warzeski
5c65627582 Revert "MM-P2P G2 Activation (#380)" (#381)
This reverts commit 8c0cafafa1.
2021-03-10 18:55:34 -05:00
Ben Warzeski
8c0cafafa1 MM-P2P G2 Activation (#380)
* upgrade paragon

* mmp2p experiment code

* mmp2p courseware triggers

* mmp2p course-home triggers

* mmp2p load styles

* mmp2p - add missed locator docstrings

* mmp2p test fixes

* add lazy loading for image-bearing components

* mmp2p experiment README

* mmp2p add lazy loading for sidecard

* generalize prices for currency options

* mmp2p fix flyover mobile args

* mmp2p fix lock paywall border display
2021-03-10 16:40:53 -05:00
Rebecca Graber
3450570d7e WS-1740 add course recommendations to celebration page (experiment) (#376)
* WS-1740 add course recommendations to celebration page (experiment)
2021-03-08 10:07:08 -05:00
edX Transifex Bot
c650283446 fix(i18n): update translations 2021-03-07 16:06:19 -05:00
Zachary Hancock
28773ce4c2 onboarding panel exam release date (#375)
* disable onboarding link for unreleased exam
2021-03-02 13:04:20 -05:00
Bianca Severino
e4b1d8088a Add "other course approved" and "expiring soon" states to proctoring info panel (#373) 2021-03-02 09:11:12 -05:00
edX Transifex Bot
1dc0669bae fix(i18n): update translations 2021-02-28 16:06:42 -05:00
Carla Duarte
58eb9fe23c AA-651: fix dates tab undefined property (#372) 2021-02-26 15:59:49 -05:00
Carla Duarte
38617c827e AA-684: sequence link to legacy or MFE (#371) 2021-02-26 12:00:10 -05:00
Matthew Piatetsky
a9939a1b5e fix three day streak bug with incorrect variable (#370) 2021-02-25 12:05:33 -05:00
Matthew Piatetsky
2525805aac feat: Create three day streak celebration (#354)
Show learners a celebratory modal if they visit the learning mfe for 3 days in a row. Call edx-platform API to determine if they should see the celebration.
AA-304
2021-02-22 14:34:28 -05:00
Ben Warzeski
26a7b3b0de tie p2p action only to targetted date-summary entry if there are multiple (#367) 2021-02-22 13:48:43 -05:00
Ben Warzeski
4bbc29591c fix conditional for injecting experiment method (#366) 2021-02-22 12:33:27 -05:00
Carla Duarte
49bfc65a03 fix null course goal object bug (#365) 2021-02-18 12:17:32 -05:00
Michael Terry
d017c3194e feat: Show effort estimation if the backend provides it (#357)
AA-614
2021-02-16 14:36:05 -05:00
Ben Warzeski
a2ccedcecd [MM-P2P] optimizely experiment overrides for course_home (#364)
* alert override

* course home date overrides

* add undefined check for experiment hook

* inject intl rather than passing
2021-02-16 12:01:04 -05:00
Renovate Bot
3a7c455bb3 fix(deps): update font awesome 2021-02-08 22:05:08 +00:00
Renovate Bot
19087417b4 fix(deps): update dependency @edx/frontend-platform to v1.8.4 2021-02-08 21:17:06 +00:00
Renovate Bot
05c6878644 fix(deps): update dependency @edx/frontend-component-footer to v10.1.4 2021-02-08 20:09:56 +00:00
Matthew Piatetsky
7e2f495f52 Use contains_content_type_gated_content attribute, rather than the graded attribute, to determine if the content type gating paywall should be displayed. (#349)
The issue was that items with the graded attribute are not always going to be paywalled by content type gating.
AA-613
2021-02-05 12:29:08 -05:00
Dillon Dumesnil
50e649daa3 AA-491: Update MFE with analytics event (#351)
These already exist in the legacy view. This is just porting them
over into the MFEs
2021-02-02 05:39:27 -08:00
Dillon Dumesnil
629382f719 AA-492: Add event data for consumption in the backend (#355) 2021-02-01 09:46:21 -08:00
edX Transifex Bot
8835a9cd6a fix(i18n): update translations 2021-01-31 16:05:18 -05:00
Simon Chen
3e2eebdd9b MST-621 (#353)
The text in the onboarding panel for submitted state is confusing. This PR updated the text so learners understood the wait
2021-01-28 11:54:05 -05:00
Carla Duarte
acd2cc3222 AA-638: course completion bug (#352) 2021-01-27 13:59:06 -05:00
Michael Terry
9ef3787d4b AA-410: Make sure alerts are cleaned up after being added (#324) 2021-01-25 12:41:32 -05:00
Carla Duarte
47fd6bfe18 AA-512: underline links within iframes (#347) 2021-01-25 11:11:14 -05:00
edX Transifex Bot
984010a8ec fix(i18n): update translations 2021-01-24 16:04:53 -05:00
Michael Terry
58543a34b3 Separate courses redux model into courseHomeMeta and coursewareMeta (#348) 2021-01-22 15:28:16 -05:00
alangsto
293dc9f4c3 Updated broken links (#346)
updated tests

added test
2021-01-21 14:54:18 -05:00
alangsto
68c8d31dd1 added component for proctoring panel (#345)
added 404 test

updated for feedback

updated test
2021-01-21 12:15:56 -05:00
Carla Duarte
96ef87886f AA-361: shift dates banner tests (#344) 2021-01-19 17:09:29 -05:00
Carla Duarte
48e3f43062 AA-598: canEnroll loop bug fix (#343) 2021-01-11 16:53:22 -05:00
Carla Duarte
d74557d681 AA-481: Course home outline UI fixes (#341) 2021-01-11 15:30:49 -05:00
edX Transifex Bot
0e18e0908a fix(i18n): update translations 2021-01-10 16:05:20 -05:00
Carla Duarte
958c13ca93 AA-465: Various a11y fixes (#340) 2021-01-07 12:11:28 -05:00
Carla Duarte
26de2cebeb AA-545: Add course in progress CourseExit variant (#334) 2021-01-06 15:10:29 -05:00
Matthew Piatetsky
bd8496a5e2 move flyover button toggle within frontend-app-learning (#339) 2021-01-06 12:31:00 -05:00
Matthew Piatetsky
aa56239f54 add empty div to fix bug with moving flyover toggle 2021-01-06 11:45:00 -05:00
Emma Green
a0b85111eb change the flyover icon underline to 2px from UX feedback 2021-01-06 10:04:22 -05:00
Dillon Dumesnil
07b252ecc6 AA-501: Send query param to recheck access to xblocks for render (#335) 2021-01-06 06:49:25 -08:00
David Joy
92b364e0f8 Upgrading frontend-platform to latest. (#336) 2021-01-05 16:14:34 -05:00
Emma Green
9247eb3098 fix grammer for value prop experiment for gated upsell 2021-01-04 11:50:09 -05:00
Emma Green
9e0f5d7e22 add black underline when value prop flyover is open 2021-01-04 11:50:09 -05:00
edX Transifex Bot
db4b4d18cc fix(i18n): update translations 2020-12-27 16:04:43 -05:00
julianajlk
92a464b2da Update REV-1512 experiment gated content (#329)
REV-1512
2020-12-22 10:08:00 -05:00
Matthew Piatetsky
7fed8db02a fix button interactivity (#328) 2020-12-21 13:01:47 -05:00
Matthew Piatetsky
8161f4d9a0 fix x button interactivity (#327) 2020-12-21 12:13:27 -05:00
Ben Holt
c6661e71b1 Add span for mobile return text and standardize on notifications title for localization (#325) 2020-12-21 11:02:39 -05:00
edX Transifex Bot
6dc7ff761a fix(i18n): update translations 2020-12-20 16:04:36 -05:00
Matthew Piatetsky
de4a7d9f34 Update business logic for the flyover per UX feedback (#316) 2020-12-17 12:51:19 -05:00
Ben Holt
5f239583fd Extract flyover component and make a version for mobile (#315) 2020-12-17 11:34:17 -05:00
Renovate Bot
aff49aa8a9 fix(deps): update dependency @edx/frontend-component-footer to v10.1.1 2020-12-17 16:24:06 +00:00
David Joy
6e10bffd40 fix: bumping frontend-build version to 5.5.5 (#319) 2020-12-17 11:22:34 -05:00
Michael Terry
37a4dcce18 AA-449: Show discounted price in upgrade buttons & course exit (#311)
If an offer is active for the user, show the discounted price (and
a struck-out original price) on upgrade buttons in the course sock
and outline sidebar.

Also show the discount code and price in the course exit upgrade
screen.
2020-12-17 10:18:57 -05:00
renovate[bot]
c16da21602 chore(deps): update dependency @edx/frontend-build to v5.5.4 (#317)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2020-12-17 10:16:33 -05:00
renovate[bot]
55ea84f9a6 fix(deps): pin dependencies (#215)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2020-12-17 10:09:05 -05:00
Carla Duarte
4341a828db AA-131: Landing page for anonymous or un-enrolled users (#281) 2020-12-16 15:50:17 -05:00
Matthew Piatetsky
255e36baa8 [REV-1517] Add Notification header and X to hide the flyover (#313)
* Add back classes to first purchase offer banner that are used in the REV-1512 optimizely test

* Add notification header with an X to hide the flyover box
2020-12-16 12:35:05 -05:00
Renovate Bot
2fc4c8c153 chore(deps): update dependency @edx/frontend-build to v5.5.3 2020-12-15 23:50:16 +00:00
Matthew Piatetsky
79f5c7fcf4 Add back classes to first purchase offer banner that are used in the REV-1512 optimizely test (#309) 2020-12-15 14:59:56 -05:00
Carla Duarte
743621ff51 Dates tab badge fix (#310) 2020-12-15 12:06:53 -05:00
Carla Duarte
9272498c9e AA-417: Update cert images (#308) 2020-12-15 10:24:27 -05:00
Michael Terry
e89aef78b5 TNL-7185: Stop using dangerouslySetInnerHTML in alerts (#306)
Render offer and access-expiration alerts ourselves from newly
passed in backend data, rather than from provided HTML blobs.
2020-12-14 16:09:49 -05:00
edX Transifex Bot
3d41c56a0a fix(i18n): update translations 2020-12-13 16:04:55 -05:00
JJ
e4060b7481 [REV-1521] Add new lock paywall component for the Value Prop experiment (#293)
Add a new lock paywall component to be shown as part of the Purchase squad's Value Prop Optimizely experiment.
2020-12-11 11:31:07 -05:00
Renovate Bot
4e92053151 Update dependency @edx/frontend-build to v5.5.2 2020-12-10 22:33:33 +00:00
Michael Terry
38700499d4 AA-450: add catalog search link to bottom of course exit (#299) 2020-12-10 11:16:35 -05:00
Matthew Piatetsky
0e3fc032ab Add flyover box and button to toggle it for the REV1512 experiment (#291)
This part would be trickier to do in optimizely so adding it in react
REV-1512
2020-12-10 11:04:05 -05:00
Dillon Dumesnil
a604e0be10 AA-503: Course Celebration view for users in verification pending state (#301)
Learners were having questions when we would continue showing them the
'Verify Now' button if they had a submitted a verification attempt
already.
2020-12-09 14:37:23 -05:00
Carla Duarte
264f36b89e AA-504: Dates tab rebrand fix (#303) 2020-12-09 14:36:39 -05:00
stvn
2319a7dfb0 Merge PR #302 kill/travis
* Commits:
  Remove Travis-CI integration
2020-12-09 10:54:31 -08:00
stvn
6549e2b8a2 Remove Travis-CI integration
This is admittedly the much abridged version, but...
Due to changes at Travis-CI that lead to prohibitively slow build/wait
times, we (edx) decided to switch CI/validation provider to Github
Actions [1].

While we had originally intended to keep the integration available,
following Travis-CI's drop in available open-source credits, it no
longer appears to be a viable option for the community.

As such, and given our already present vendor lock-in at Github,
we recommend using Github Actions for CI/validation in this repo.

However, for those not hosting in the Github ecosystem, you can
configure your testrunner to run `make validate.ci`, for the same
effect; the Github Action is just a wrapper for this call.

- [0] Fixes: TNL-7784
- [1] #276
2020-12-09 10:44:32 -08:00
David Joy
ddfc88dad6 Miscellaneous rebranding fixes (#300)
* Rebranding course license

- Fixing horizontal padding
- Setting color to gray 500
- Removing unnecessary a:hover color
- Removing unneeded CSS classes (sequence-footer, course-license, and license-text)
- Removing unnecessary spans

* Removing all usages of theme-color.

* Removing underline from “Help” button in header

* Use outline-primary for user menu button in header.

* Revert text-decoration-none on Help link

This is unnecessary now - it’s fixed at the brand level.

* Rebranding breadcrumbs to be primary-500.

* Set styling for Unit title to h3.

* Setting bookmark button color to primary-500

* Aligning current selection line to be inside dropdown, not outside.

* Adjusting sequence dropdown colors

Gray 700 for non-active units, primary 500 for active unit icon.

* Remove custom btn-outline-light config

We no longer use it in this MFE and the color palette in paragon/the brand has changed anyway.

* Let the stylesheet breathe!
2020-12-08 17:13:23 -05:00
stvn
e1e3d4992d Merge PR #298 fix/codecov
* Commits:
  Run coverage via Github Action
2020-12-08 10:09:57 -08:00
David Joy
43b0f2fbf0 Darken sequence nav buttons so they don’t appear disabled (#297) 2020-12-08 12:47:48 -05:00
stvn
64ff72faa9 Run coverage via Github Action
Note: I tried directly invoking `codecov`, but it "couldn't tell" that
it was running in a GA/CI container, so I've opted for using the GA
codecov integration, which does work as expected.
2020-12-08 09:31:27 -08:00
stvn
cc0af77e2f Merge PR #276 add/github-workflow
* Commits:
  Add Github workflow for CI tests
  Use common CI test target via Travis
  Consolidate CI test scripts in Makefile
2020-12-08 08:55:29 -08:00
stvn
7086bdc9ab Add Github workflow for CI tests 2020-12-08 08:48:56 -08:00
stvn
e627fd6f27 Use common CI test target via Travis 2020-12-08 08:47:31 -08:00
stvn
2ba9440966 Consolidate CI test scripts in Makefile 2020-12-08 08:46:57 -08:00
David Joy
cd51206462 Fixes tab color, according to rebrand UX feedback (#296) 2020-12-08 11:13:12 -05:00
Michael Terry
0cb97db7eb AA-383: add outline sidebar upgrade card (#289)
Also adds the course sock to the outline page.
2020-12-08 10:58:57 -05:00
Renovate Bot
421b438569 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.13 2020-12-08 00:29:54 +00:00
Renovate Bot
1d40baf4cd chore(deps): update dependency es-check to v5.1.4 2020-12-07 23:36:14 +00:00
David Joy
3c5fb46a4d Update MFE to use the new “brand-openedx” repository. (#283)
* Update MFE to use the new “brand-openedx” repository.

This will allow the MFE to be re-branded by overriding this default implementation.  More detail here:

https://github.com/edx/brand-openedx

* Removing unused frontend-component-header module.

This app doesn’t use frontend-component-header.  That it was a dependency is confusing and led me to believe I needed to wait for its rebrand to continue - not so.  Removing the unused dependency.

* Adding quick comment describing the structure of gated_content

* Fixing course exit styling

* Changing LinkedIn icon

* fix: fix Instructor toolbar button styling

* Bumping footer, platform, and paragon versions.

* Using configured logo and favicon.

Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-12-07 17:50:06 -05:00
Michael Terry
5f06d726f7 AA-478: correctly refresh outline data when refreshing dates (#292) 2020-12-07 14:09:44 -05:00
Michael Terry
bcd69f5836 AA-463: move welcome message below banners on outline tab (#290) 2020-12-07 09:51:30 -05:00
Michael Terry
654fd4c35c AA-483: Fix some course exit segment event calls to work again (#288) 2020-12-04 10:33:45 -05:00
Michael Terry
b98a87c1f5 AA-476: Send org_key with tracking events (#286)
Also convert the course tool click event from accidentally being
a segment event into a log event, like it is on the LMS.
2020-12-03 12:44:52 -05:00
Zainab Amir
0d29082793 Add suppression to username in header (#287) 2020-12-03 15:39:16 +05:00
Dillon Dumesnil
2d56bc5953 AA-448: UI fixes (#285)
LinkedIn button size and single column for view grades button in mobile
2020-12-02 04:59:16 -08:00
David Joy
1bfe3f4436 Use prereqSectionName for the name of the prereq section. (#284)
Surprise, surprise.

Not sure how this wasn’t correct.  The bug results in the content lock UI showing the name of the current section rather than the prerequisite when describing… the prerequisite.
2020-12-01 10:04:59 -05:00
edX Transifex Bot
3f9f40800a fix(i18n): update translations 2020-11-22 16:04:56 -05:00
Renovate Bot
a1c7969477 fix(deps): update dependency @edx/frontend-component-header to v2.0.6 2020-11-19 20:24:49 +00:00
renovate[bot]
53cf637938 chore(deps): update dependency @edx/frontend-build to v5.4.0 (#247)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2020-11-19 13:13:22 -05:00
stvn
329bcba31c Merge PR #275 add/event/tracking-logs
* Commits:
  Submit events to the tracking logs, not just Segment
2020-11-17 12:46:26 -08:00
stvn
122cef6053 Submit events to the tracking logs, not just Segment
Due to an unfortunate naming choice, we apparently had _not_ been
sending navigation events to the tracking logs' `/track` backend, but
just to the Segment.io tracking backend.

Note: There are still other portions of this repo _outside_ the learning
sequence (Course Tools, Celebration, etc.) that only send to Segment and
not the tracking logs, though maybe this is by design, those being "new"
events?
2020-11-17 11:05:14 -08:00
Carla Duarte
a8a8cf5862 AA-199: Program Completion in CourseExit page (#271) 2020-11-16 15:03:45 -05:00
edX Transifex Bot
74149c2c54 fix(i18n): update translations 2020-11-15 16:04:56 -05:00
Michael Terry
15975fdd78 AA-356: add more config overrides for branding (#270)
Remove some edX-specific branding / links (like support URLs) in
favor of values from configuration.

Images (sample certificates) are still branded for now. We can get
them later.
2020-11-10 10:24:33 -05:00
David Joy
99f0a4a208 fix: make LTI modals full-screen (#268)
Fixes TNL-7410

This causes LTI modals in courseware to take up the whole screen.  It does this by creating a new “dialogClassName” value that we then use to override the default heights/widths of the Bootstrap modal.

We also remove the title of the iframe, which just takes up space and detracts from the LTI content.
2020-11-03 16:04:41 -05:00
edX Transifex Bot
4f9cd060be fix(i18n): update translations 2020-11-01 16:04:56 -05:00
Michael Terry
cd1d3dd379 AA-402: send segment events for course-exit (#265)
Specifically, a "I visited this page" event, with information on
which variant was seen. And "I clicked this button" events, for
our various calls to action.
2020-10-29 09:55:14 -04:00
Renovate Bot
b08f3d7b45 chore(deps): update dependency es-check to v5.1.2 2020-10-28 01:24:55 +00:00
Carla Duarte
1531f3e912 AA-390: Add Social Share icons to Course Exit (#259) 2020-10-27 14:58:35 -04:00
Michael Terry
6f415544be AA-197: Handle non-cert learners that can upgrade (#263)
Tell them about verified certificates and link to ecommerce.

Also fixes AA-376 by handling the no-verified-mode-to-upgrade-to
case.
2020-10-27 11:28:52 -04:00
Renovate Bot
4eb52a592d fix(deps): update dependency react-redux to v7.2.2 2020-10-27 00:35:14 +00:00
Renovate Bot
b4823b90e7 fix(deps): update dependency @fortawesome/react-fontawesome to v0.1.12 2020-10-26 15:20:56 +00:00
edX Transifex Bot
5602c0a3b3 fix(i18n): update translations 2020-10-25 17:04:55 -04:00
Dillon Dumesnil
e0e53f24f1 AA-397: Course Exit Tests (#257)
This also renames the CourseExit url from /course/{course_id}/course-exit
to /course/{course_id}/course-end for better UX.
2020-10-23 10:01:42 -07:00
Renovate Bot
9d8c687e4d fix(deps): update dependency @edx/paragon to v12.0.5 2020-10-23 15:50:47 +00:00
Michael Terry
aeb6a3ebb4 Revert "Revert "AA-403: use PageRoute, not Route (#252)" (#253)" (#254)
This reverts commit e2aa00b16d.
2020-10-20 15:20:10 -04:00
Michael Terry
e2aa00b16d Revert "AA-403: use PageRoute, not Route (#252)" (#253)
This reverts commit ac711d5f3d.
2020-10-20 15:06:05 -04:00
Michael Terry
ac711d5f3d AA-403: use PageRoute, not Route (#252)
This way we get frontend-platform's automatic segment page tracking.
2020-10-20 14:38:19 -04:00
Dillon Dumesnil
9d8b5d21b5 AA-385: Add LinkedIn Add to Profile Button to Course Celebrate (#249) 2020-10-20 07:30:18 -07:00
Michael Terry
15ae6d4981 AA-362: Add tests for outline tab alerts (#250) 2020-10-20 10:03:30 -04:00
Carla Duarte
f063495cbb AA-381: Fix shorten welcome message bug (#251) 2020-10-19 16:31:05 -04:00
Michael Terry
c5821faee8 AA-377: Add non-passing course exit screen (#246)
- Adds a non-passing cert learner course exit screen
- Moves all the logic about what course-exit mode we're in into
  a utility method in the course-exit folder
- Moves all the logic about how the 'Next' button should read into
  a utility method in the course-exit folder
2020-10-19 11:00:44 -04:00
edX Transifex Bot
f83a6e574c fix(i18n): update translations 2020-10-18 17:04:55 -04:00
Renovate Bot
03661ccf4b fix(deps): update dependency @edx/frontend-platform to v1.5.4 2020-10-15 22:24:59 +00:00
Carla Duarte
2d5af74b1b AA-357: Add reset dates banner to outline tab (#243) 2020-10-15 13:44:04 -04:00
Carla Duarte
ae8141c1a8 AA-388: CourseCelebration UI improvements (#242) 2020-10-15 10:37:53 -04:00
Carla Duarte
e9cf5e58de AA-388: CourseExitPage UI improvements (#237) 2020-10-14 10:34:55 -04:00
David Joy
1950fe56bd TNL-7299 - Hide sequences that are time limited (special exams) (#241)
If a sequence has its isTimeLimited flag set, then show a spinner instead of the sequence content.  The CoursewareContainer, meanwhile, will be attempting to redirect to the legacy experience.

This prevents a situation where we temporarily show proctored/special exam content to users before their exam starts.
2020-10-14 10:11:14 -04:00
Renovate Bot
94cacb14e7 chore(deps): update dependency @edx/frontend-build to v5.2.7 2020-10-10 16:04:46 +00:00
Renovate Bot
a8dea78e24 chore(deps): update dependency @edx/frontend-build to v5.2.6 2020-10-10 15:17:05 +00:00
Renovate Bot
8adcfb040a fix(deps): update dependency @edx/paragon to v12.0.4 2020-10-07 18:05:43 +00:00
David Joy
4f396737e4 Fixes TNL-7613 and TNL-7614 (#236)
The course blocks API accepts a value of ‘special_exam_info’ in its requested_fields parameter.  This value is necessary to include special exam sequences for non-staff users.  Special exams include timed, proctored, and practice proctored exams.
2020-10-06 17:17:29 -04:00
Dillon Dumesnil
36f8dd81cd Quick fix to add slice to other routes using TabContainer (#235) 2020-10-06 11:57:18 -07:00
Dillon Dumesnil
f6aebc7d29 AA-389: Updating Routing for CourseExit Page (#234)
The CourseExit page uses the TabContainer because it seems to be a better solution
than adding it into the paths for the CoursewareContainer, despite the CoursewareContainer
already doing the correct fetch. The reason for this is because within the
CoursewareContainer, it would still be necessary to check for the /course-exit path (due
to the CoursewareContainer trying to greedily match sequenceId (read: it will try and
look up 'course-exit' as a sequence)). That effectively defeats the purpose of using the
routing in the first place so instead, we place it in a TabContainer.

The InstructorToolbar didMount logic became necessary once we had a page (CourseExit) that does a redirect on a quick exit.
As a result, it unmouunts the InstructorToolbar (which will be remounted by the new component),
but the InstructorToolbar's MasqueradeWidget has an outgoing request. Since it is unmounted
during that time, it raises an error about a potential memory leak. By stopping the render
when the InstructorToolbar is unmounted, we avoid the memory leak.
2020-10-06 10:42:48 -07:00
Renovate Bot
8a63aef3f0 fix(deps): update dependency @fortawesome/fontawesome-svg-core to v1.2.32 2020-10-05 20:07:48 +00:00
edX Transifex Bot
4be37ceb14 fix(i18n): update translations 2020-10-04 17:09:55 -04:00
Carla Duarte
d123fe6229 AA-363: Tests for Outline Tab and Widgets (#222) 2020-10-02 16:48:42 -04:00
Nick
2f738fdba4 AA-196 course celebration cert (#197)
* AA-196 course celebration cert

* AA-196: Course Celebration for passing Verified Learners

Co-authored-by: Dillon Dumesnil <ddumesnil@edx.org>

Note: This PR is being merged in somewhat incomplete as we decided to split off the work into a couple of other tickets. For example, the UI styling is not complete and I plan to also take another look at the routing. These code paths are not in use yet as the `courseExitPageIsActive` will always be False.
2020-10-02 07:27:59 -07:00
Michael Terry
d52aa3246e Revert "Revert "AA-291: Add Optimizely (#219)" (#227)" (#229)
This reverts commit 42715d3de2.
2020-10-01 10:38:09 -04:00
stvn
e6e5258e5b Merge PR #228 log/unexpected-course-block
* Commits:
  Log "Section ... has child block" as info, not error
  Log "Unexpected course block type" as info, not error
2020-09-30 14:59:53 -07:00
stvn
753925ba99 Log "Section ... has child block" as info, not error 2020-09-30 14:03:01 -07:00
stvn
684be8c0cf Log "Unexpected course block type" as info, not error 2020-09-30 13:36:22 -07:00
Michael Terry
42715d3de2 Revert "AA-291: Add Optimizely (#219)" (#227)
This reverts commit f91abd319f.
2020-09-29 16:05:13 -04:00
Renovate Bot
4d21633462 fix(deps): update dependency @fortawesome/fontawesome-svg-core to v1.2.31 2020-09-29 17:16:25 +00:00
Carla Duarte
f91abd319f AA-291: Add Optimizely (#219) 2020-09-29 11:19:22 -04:00
Renovate Bot
43be11c636 fix(deps): update dependency @edx/paragon to v12.0.1 2020-09-28 20:15:39 +00:00
edX Transifex Bot
72df79b9b8 fix(i18n): update translations 2020-09-27 17:04:45 -04:00
Michael Terry
6f331ea6d5 AA-253: handle the backend giving less data (#223)
Specifically, no outline tree or no course tools
2020-09-25 16:50:30 -04:00
Renovate Bot
d137d5682d chore(deps): update dependency @edx/frontend-build to v5.2.3 2020-09-25 14:39:48 +00:00
Renovate Bot
ea9f5254b7 fix(deps): update dependency @edx/frontend-platform to v1.5.3 2020-09-23 03:24:13 +00:00
Renovate Bot
577a19e35c chore(deps): update dependency es-check to v5.1.1 2020-09-22 06:31:10 +00:00
edX Transifex Bot
f58d405b3b fix(i18n): update translations 2020-09-20 17:04:33 -04:00
David Joy
927d424d33 Agrendalath/bb 2599 low priority tests (#214)
* [TNL-7269] WIP low priority tests

* [TNL-7269] Add low priority tests

* [TNL-7269] Fix failing EnrollmentAlert tests

* [TNL-7269] Address review comments

* Fixing test errors on rebase with master.

Co-authored-by: Agrendalath <piotr@surowiec.it>
2020-09-18 09:27:41 -04:00
Kyle McCormick
25e5d39a72 Refer to 'Existing experience' as 'Legacy experience' (#213) 2020-09-17 13:50:57 -04:00
Michael Terry
d8ed3d6bf8 AA-265: Add more tests for the dates tab (#207)
* Prepare some initial test refactoring

- Expand test data for course home metadata
- Don't test courseware metadata in course-home redux tests

This is Dillon's work.

* AA-265: Add more tests for the dates tab

Add an ADR to talk about how we want to test in this repo.

And refactor the fake dates tab data used for debugging to also be
used for tests.
2020-09-17 11:01:33 -04:00
Carla Duarte
c5a43524a1 AA-368: Expand course outline section that contains resume block (#212) 2020-09-16 13:46:06 -04:00
Alex Dusenbery
adfc2d568b fix: bump frontend-enterprise to 4.2.3 2020-09-16 10:03:21 -04:00
Carla Duarte
4c6797c631 AA-128: Course Home UI/UX improvements (#208) 2020-09-16 09:29:58 -04:00
edX Transifex Bot
e2710f6ed3 fix(i18n): update translations 2020-09-13 17:05:12 -04:00
Jeff LaJoie
1b859f4ab6 ENT-3181: Enables custom branding for enterprise customers in logo and user menu 2020-09-11 09:15:39 -04:00
Dillon Dumesnil
f44ce4c311 AA-320: Adding in UTM parameters to sharing links (#204) 2020-09-10 08:14:30 -07:00
edX Transifex Bot
d8dbbaa7a2 fix(i18n): update translations 2020-09-06 17:04:58 -04:00
Michael Terry
ddc85f2fd3 AA-306: Update twitter hashtag for celebrations (#201)
It's now #myedxjourney instead of #mooc.

Also, fixed the wording of the email share option.
2020-09-03 11:17:42 -04:00
Michael Terry
9cbe0b7c8b AA-126: Update outline tab to be a little closer to LMS (#198)
- Changed it to use its own normalizeBlocks call (and stop sharing
  with courseware)
- Add green checkmarks for complete blocks
- Added icons, descriptions, and due dates for subsections
- Updated look of subsections to match LMS a bit more
2020-08-31 16:44:30 -04:00
Carla Duarte
c83389e7c5 AA-125: quick fix (#199) 2020-08-31 13:50:20 -04:00
Carla Duarte
37d56b4197 AA-125: Course Goals in Course Home Outline Tab (#190) 2020-08-31 09:58:34 -04:00
Renovate Bot
01f69e2273 fix(deps): update dependency @edx/paragon to v10.1.1 2020-08-27 21:21:46 +00:00
Michael Terry
c5ada7e974 AA-326: hide dates banner if the upgrade link is null (#195)
This will prevent a pointless & broken button when the upgrade
deadline has passed for the course.
2020-08-25 09:04:06 -04:00
edX Transifex Bot
450d1c1861 fix(i18n): update translations 2020-08-23 17:04:33 -04:00
David Joy
aa10b3f600 Use ‘modal-lg’ for LTI modals. Update Paragon. (#193)
The new version of Paragon includes a “dialogClassName” property on Modal which lets us set the modal width to ‘modal-lg’ or ‘modal-sm’ - we’re using the former here.
2020-08-21 16:28:02 -04:00
Michael Terry
b65bd0ff44 AA-305: Update Masquerade Widget UI (#188)
* AA-305: Update Masquerade Widget UI

This will start showing the user being masqueraded as in the Toolbar
and will show any error messages next to the Toolbar as well.

* Further masquerade widget fixes

Co-authored-by: Dillon Dumesnil <ddumesnil@edx.org>
2020-08-21 12:49:12 -04:00
David Joy
86d28136de Paragon 10: Updating Dropdown and Button usages. (#187)
* Updating Dropdown and Button.

* Fixing broken tests and test warnings.

* Remove comment block.

* Using variant=“link” on the Tabs Dropdown.Toggle.

* Fixing some merge conflicts.
2020-08-20 13:46:44 -04:00
Alex Dusenbery
707fcc2aa1 feat: ent-3273 | Don't show order history in dropdown if user is associated with a subscription
Update frontend-enterprise dependency to 4.2.2
2020-08-20 11:31:52 -04:00
Kyle McCormick
caabf6a54c Prepend dev BASE_URL with http:// to fix login redirects (#189) 2020-08-20 08:35:16 -04:00
Dave St.Germain
d910d09e00 Allow units to create a modal in the parent window (#184)
This is used by LTI blocks configured to launch in a modal window. Instead of opening a modal in our unit iframe, the component will send a message to the parent window (the courseware mfe) requesting it to open its own modal, containing a URL to launch the LTI tool.
2020-08-19 13:08:13 -04:00
edX Transifex Bot
de53ed9258 fix(i18n): update translations 2020-08-16 17:08:26 -04:00
daphneli-chen
81188ae30f AA-274: Created credit requirements banner (#168)
Co-authored-by: Daphne Li-Chen <dli-chen@edx.org>
2020-08-14 16:50:10 -04:00
stvn
f5d361661f Merge PR #183 add/kill-switch
* Commits:
  Implement kill-switch for non-staff users
2020-08-13 14:28:30 -07:00
David Joy
c298bc1dbf Implement kill-switch for non-staff users
to redirect to the current unit’s lmsWebUrl if the MFE is disabled

If we receive an error_code of 'microfrontend_disabled',
go to the equivalent unit in the LMS experience.

Fixes: TNL-7362
Co-authored-by: stvn <stvn@mit.edu>
2020-08-12 17:26:30 -07:00
Kyle McCormick
0ad80a63cf Fix lint warnings; require 0 warnings for Travis build (#181) 2020-08-12 17:16:28 -04:00
Kyle McCormick
bc76adf8eb Wrap all alert payloads in useMemo to avoid infinite re-rendering (#182)
* Wrap all alert payloads in useMemo to avoid infinite re-rendering

This manifested in production as a browser freeze for any
user who saw the 15%-off-to-upgrade message.

TNL-7400

* fixup! meant to say identity, not equality
2020-08-12 17:12:31 -04:00
David Joy
cc7142e5c1 Fix checkBlockCompletion parameters
We were assuming a prop named unitId existed in CoursewareContainer - it doesn’t.  unitId is not in redux.  What we do have, is the unitId in the route params - what we refer to as routeUnitId.  If we use this instead of the non-existent unitId, then life is good.

I wrote a test (that breaks!) prior to implementing the fix.  The fix satisfies the test. 🎉
2020-08-12 16:01:12 -04:00
Dillon Dumesnil
a975b8ae70 Format may not be defined. Provide a default and only add to URL if it exists (#178) 2020-08-12 09:49:21 -07:00
Renovate Bot
3b34a87391 Pin dependency @edx/paragon to 9.1.1 2020-08-12 12:38:20 -04:00
Nick
453f56c7c8 AA-287 cta events (#177) 2020-08-12 11:48:46 -04:00
Dillon Dumesnil
a131a9f9fb Add format into iframe request to populate context (#174)
The format is used with the due date in the vertical to show
text like "Homework due ___"
2020-08-11 08:32:24 -07:00
Carla Duarte
d5f9af1954 AA-129: Resume Course button (#133) 2020-08-11 11:08:29 -04:00
Carla Duarte
a44d2633a1 AA-255: Dates Tab Tooltip for ORA (#173) 2020-08-10 09:37:01 -04:00
edX Transifex Bot
de2a46eb93 fix(i18n): update translations 2020-08-09 17:08:11 -04:00
Nick
9a315aa29d AA-264 mfe courseware reset dates (#165)
- add toast display after successful reset of dates via banner.
2020-08-06 15:20:53 -04:00
stvn
ad74b2295b Merge PR #106 add/masquerade-username
* Commits:
  Add "masquerade as specific student" support
2020-08-06 12:06:47 -07:00
stvn
c8961d3777 Add "masquerade as specific student" support 2020-08-06 09:07:53 -07:00
Dillon Dumesnil
c7c401e385 AA-275: Persist if the original user was staff for Instructor Toolbar (#167)
We want to be able to know if both the original user is a Staff user
as well as if the user being masqueraded as is staff. This updates
to accept both of these fields
2020-08-06 08:58:47 -07:00
Michael Terry
9e0f192ae7 AA-278 & AA-279: Add offer and course expired alerts to outline (#164)
* AA-278: Add offer alert to outline

It was previously only used in the courseware. But to match the
LMS, we also want to show it on the outline tab.

* AA-279: Add course expired alert to outline

It was previously only used in the courseware. But to match the
LMS, we also want to show it on the outline tab.
2020-08-06 09:49:37 -04:00
daphneli-chen
4667535c0c AA-211: Created certificate banner and studio link (#134)
Co-authored-by: Daphne Li-Chen <dli-chen@edx.org>
2020-08-05 10:26:34 -04:00
Renovate Bot
a1646c5793 Update dependency regenerator-runtime to v0.13.7 2020-08-04 06:58:42 +00:00
Renovate Bot
0e6f64081d Update dependency react-share to v4.2.1 2020-08-04 04:38:41 +00:00
Renovate Bot
8e4f0535a7 Update dependency react-redux to v7.2.1 2020-08-04 03:20:10 +00:00
Renovate Bot
6f14f01dc2 Update dependency codecov to v3.7.2 2020-08-04 01:42:40 +00:00
Renovate Bot
6233f16812 Update dependency @testing-library/user-event to v12.0.17 2020-08-04 00:10:16 +00:00
Renovate Bot
a60480ec52 Update dependency @fortawesome/fontawesome-svg-core to v1.2.30 2020-08-03 22:15:38 +00:00
Renovate Bot
eba9d9a44d Update dependency @edx/frontend-platform to v1.5.2 2020-08-03 20:30:07 +00:00
Renovate Bot
8cd3d6501f Update dependency @edx/frontend-component-footer to v10.0.11 2020-08-03 19:52:06 +00:00
dependabot[bot]
76fc6d64f2 Bump codecov from 3.7.0 to 3.7.1
Bumps [codecov](https://github.com/codecov/codecov-node) from 3.7.0 to 3.7.1.
- [Release notes](https://github.com/codecov/codecov-node/releases)
- [Commits](https://github.com/codecov/codecov-node/compare/v3.7.0...v3.7.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-03 14:40:37 -04:00
Michael Terry
314b82d0b2 AA-276: Add course start alert in outline (#146)
Also move a few of the outline alerts into the course home
source folder.
2020-08-03 13:39:25 -04:00
Michael Terry
e17b66851e AA-277: Add certificate available alert on outline (#144) 2020-08-03 13:04:47 -04:00
Renovate Bot
9accacd019 chore(deps): update dependency @testing-library/dom to v7.16.3 2020-08-03 16:23:38 +00:00
Renovate Bot
a9cec0102b chore(deps): pin dependencies 2020-08-03 10:16:24 -04:00
Michael Terry
e4d6d37c4e AA-124: Show course end alert (#135)
This is a parity-with-LMS change, to bring their warning about
the course ending within a couple weeks to the MFE.
2020-08-03 09:53:40 -04:00
edX Transifex Bot
7ad501d73b fix(i18n): update translations 2020-08-02 17:07:56 -04:00
David Joy
f1d43b18d6 Tweak CoursewareContainer tests to get them working again.
This is effectively fixing merge conflicts between:

https://github.com/edx/frontend-app-learning/pull/128

and:

https://github.com/edx/frontend-app-learning/pull/97
2020-07-31 16:02:38 -04:00
Agrendalath
d408986682 [TNL-7268] Fix tests after rebase 2020-07-31 16:02:38 -04:00
Agrendalath
8d7fbb5bd8 [TNL-7268] refactor tests to use factories 2020-07-31 16:02:38 -04:00
Agrendalath
7c046870e3 [TNL-7268] Remove icon mock
As we're not using snapshots, we will not need this anymore.
2020-07-31 16:02:38 -04:00
Agrendalath
0c5fd44d13 [TNL-7268] Fix tests after rebase 2020-07-31 16:02:38 -04:00
Agrendalath
f3d23abe84 [TNL-7268] Fix imports, ignore implicit exports for tests 2020-07-31 16:02:38 -04:00
Agrendalath
6a44d018d8 [TNL-7268] Remove unused dependency 2020-07-31 16:02:38 -04:00
Agrendalath
3362047bcc [TNL-7268] Replace snapshots with specific assertions 2020-07-31 16:02:38 -04:00
Agrendalath
c1bf77efa4 [TNL-7268] Fix tests after rebase 2020-07-31 16:02:38 -04:00
Agrendalath
e6443ae3bd [TNL-7268] Address the review comments 2020-07-31 16:02:38 -04:00
Agrendalath
6f7ec81197 [TNL-7268] Fix tests and dependencies after rebase 2020-07-31 16:02:38 -04:00
Agrendalath
8719fad091 [TNL-7268] Add high priority tests 2020-07-31 16:02:38 -04:00
Agrendalath
ec7f532bc9 [TNL-7268] WIP: Add high priority tests 2020-07-31 16:02:38 -04:00
Dillon Dumesnil
0c8389e244 AA-280: Add assignment type to dates tab display (#137) 2020-07-31 12:09:47 -07:00
Michael Terry
cd8f3072e2 AA-124: Refactor enrollment alerts (#126)
- Place them only on the Outline page
- Support a few cases where enrollment isn't actually allowed
2020-07-30 12:51:17 -04:00
David Joy
b048ca8187 Fixes saving unit position and unit redirection bugs (#128)
* Bumping axios-mock-adapter version

Thought there was a feature in 1.18.2 that I needed - turns out the feature hasn’t been released yet.  Still fine to bump the dependency, though.

* Hiding some warnings about console logging.

* Fixes bugs in CoursewareContainer

Fixes a few bugs in the courseware container:

- Position was not being saved because we weren’t reading “saveUnitPosition” correctly.
- We weren’t calling checkContentRedirect with the right arguments - it was using a non-existent unitId instead of the routeUnitId, meaning we would redirect to the active unit even if a unit was specified in the URL.

Adds tests in CoursewareContainer for various URL and data states.

Now explicitly tests:
- Exam redirects
- The resume block method when it has, and doesn’t have, a block to resume.
- The content redirect when a unit isn’t present on the URL (uses sequence.position)
- Loading a specific unit (not the first of a sequence!) by URL.

Updated some of the factories to be more flexible/allow multiple units.
2020-07-29 14:24:39 -04:00
daphneli-chen
71482f1ec7 AA-205: created chapters and subsections containing progress (#109)
Co-authored-by: Daphne Li-Chen <dli-chen@edx.org>
2020-07-28 12:53:49 -04:00
Nick
7a108728c0 AA-214 social buttons (#127)
- social sharing buttons for linkedin and email
2020-07-28 12:00:26 -04:00
Michael Terry
d7f41fd02a Enable handouts on outline page (#125) 2020-07-27 10:01:18 -04:00
edX Transifex Bot
0e7bccef0b fix(i18n): update translations 2020-07-26 17:07:31 -04:00
Michael Terry
5ac49610da Re-enable the celebration modal (#123)
It got accidentally disabled during a refactor.

Also, try a little harder to make sure it doesn't re-appear during
the same browsing session.
2020-07-24 13:02:50 -04:00
Carla Duarte
175675da55 AA-127: Fix outline tab redirect URL (#122)
Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-07-23 16:01:46 -04:00
Nick
f715fd5ed6 AA-123 welcome message (#121) 2020-07-23 12:28:56 -04:00
Carla Duarte
cdab8959ca AA-218: Course Tool Analytics (#118)
Tracking analytics for onClick events in the Course Tool widget.
Extra: Fixed intl error in the Course Dates widget.

Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-07-23 10:40:52 -04:00
David Joy
be0ee18519 fix: Use reselect’s defaultMemoize instead of lodash.memoize (#120)
* fix: Use reselect’s defaultMemoize instead of lodash.memoize

Lodash memoize doesn’t examine all parameters when deciding to memoize, apparently, meaning it doesn’t re-call the function if any parameter except the first changes.

More here: https://dev.to/nioufe/you-should-not-use-lodash-for-memoization-3441

* Fixing test setup.  Improper use of sequenceMetadata factory!

Two problems:

One, we weren’t properly passing the courseId into our sequenceMetadata factories, so it was differing than the one defined in courseMetadata.  This didn’t manifest until now because we weren’t using the one from sequenceMetadata until this memoization fix!

Two, I’d updated the options for sequenceMetadata to have a “unitBlocks” option, but didn’t update all the usages of the old “unitBlock” option.  This meant it was manually creating its own unit instead of inheriting the one from courseBlocks, resulting in a different ID.
2020-07-22 10:19:53 -04:00
David Joy
c96cd87967 Change CoursewareContainer into a class component. (#115)
I find it much more legible this way.

Some thoughts… as part of refactoring it, I made some of the redux selectors more formal, and made use of reselect more thoroughly. this resulted in a reduction in re-renders from 16 to 12 on your average page load. It’s also a bit more verbose, accounting for some of the increased line count.

I hadn’t tried it before, but found the memoize method of comparing previous props/state to current props/state to be very, very nice. Much easier than manually comparing props, and much clearer to me than using react hooks’ dependency arrays.

The lack of dependency arrays feels really freeing in general to me. They’ve been such a source of hard-to-track-down bugs, and the hooks linter does not always suggest the right solution for what belongs in and out of the array.

Function names are nice. We had a ton of custom hooks in there so that we could put names to otherwise anonymous bits of functionality.

Also note: this component has a test suite. It passed without any changes. 🥳
2020-07-21 09:31:12 -04:00
Calen Pennington
854020dd67 Fix test failures due to changed snapshots, and add representative data for extra_info 2020-07-20 15:45:49 -04:00
Calen Pennington
d320c6b5bc Include assignment extraInfo on dates page 2020-07-20 14:01:02 -04:00
Carla Duarte
81c6b401fd AA-230: Dates Tab MFE styling (#111)
Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-07-20 13:39:56 -04:00
Michael Terry
16bd20e0e8 AA-121: Support showing handouts in the course outline (#112)
And add translation support for the outline.
2020-07-20 09:24:26 -04:00
edX Transifex Bot
23ea255674 fix(i18n): update translations 2020-07-19 17:07:19 -04:00
edX Transifex Bot
dec5340bf3 fix(i18n): update translations 2020-07-17 15:23:15 -04:00
Patrick Cockwell
086b5d8986 TNL-7303 Add Course License details (#103) 2020-07-17 13:26:21 -04:00
David Joy
b940901400 fix: Use activeUnitIndex instead of position, and remove the latter (#110)
We were inconsistently using “position” - a 1-indexed value - in JS arrays which are 0-indexed.  We had an existing, normalized property called “activeUnitIndex” which we now use everywhere instead.  The value is modified back to 1-indexed before being returned to the server.
2020-07-16 10:14:18 -04:00
David Joy
afb4b77250 CoursewareContainer tests. (#108)
* Adding testing-library dependencies, and bumping frontend-build to be compatible with them.

* Adding a function to initialize the redux store

We need to use it in a few places.  Seems worth not-repeating, since they can easily get out of sync.  In general, tests should only test the parts of the store they care about, as well.

* Adding function to initialize a mock application.

Ultimately I’d like to move this to frontend-platform as an alternative to ‘initialize’ for tests.  ‘initialize’ is an async function which complicates matters.

* Using more explicit assertions for courseware reducer fields.

This removes the need for the snapshot file, and ensures our test is more resilient to unrelated changes in the store.

Also added a few more stages of assertions to some of the tests, showing that they have the right values over time.

* Adding a helper to build a simple course blocks response.

We can use this in the courseware data tests, and shortly in the tests for CoursewareContainer.

* Modifying sequenceMetadata factory to allow multiple units.

This will help us test sequence navigation’s behavior more fully by having multiple units in a sequence.

* A little linting and cleanup.

* Adding first round of tests for CoursewareContainer.

Tests loading, sequence navigation/unit rendering, and ‘denied’ states.

Subsequent tests will add tests for handlers.
2020-07-15 10:27:48 -04:00
Carla Duarte
bc30b20b0d AA-221: Pull in has_ended variable for dates banner logic (#107)
Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-07-14 15:58:48 -04:00
Bill Currie
3e14b17271 [BD-29] [TNL-7288] Fix front-end behavior when the course has no sections or no subsections in the first section (#98)
* Do not redirect when the sequenceId is not valid

That is, if firstSequenceId is null or undefined. This prevents the url
becoming bogus but does cause the course contents display to become stuck
with the loading message.

* Detect invalid sequence when loading

If the course has no sections or the first section has no sub-sections,
then sequence will be null. Before the redirection fix, this would cause an
error, but after, the sequenceStatus never leaves the loading state. Thus,
if still loading and the sequence is null, return the no.content message.

* Check sequenceId instead of sequence

From David Joy:

During initial page load, I expect there's a period of time before the
course blocks API and the sequence metadata API come back where the
sequenceStatus is loading but the sequence is still false, meaning that
we'll see a flash of this 'no content' messaging for a moment before the
data comes in.

If we instead check whether sequenceId is null here, that may give us a
more accurate condition. The sequenceId in redux is only populated when we
begin to request a sequence (fetchSequence thunk). If we have no sequence
ID in the URL route, then fetchSequence never happens and the sequenceId in
redux stays null.

* Fix up some additional errors Piotr found

This fixes errors caused by deleting units or subsections.

* Move test for unit validity to SequenceContent
2020-07-14 15:21:33 -04:00
Kyle McCormick
c0d0895630 Do not assume HTML blocks are units (#105)
After further conversation, we decide that
we shouldn't just treat HTML blocks as units.
We've been making the assumption that Verticals
occupy the Unit-level, and we shouldn't break that
assumption for this specific case.

Reverts part of e04f588d1f.
However, the error handling changes remain.
2020-07-10 09:05:04 -04:00
Michael Terry
c2f4ba3ad0 AA-195: Don't show schedule banner if no schedule (#100)
In some cases (user schedule is close to or past the course end),
we don't actually have dates for a course, even if we would normally
have them.

When that happens, let's not show a banner saying we made a
schedule for the learner.
2020-07-09 16:10:49 -04:00
Demid
eac9bf9c92 [BD-29] [TNL-7266] Learning MFE data integration tests (#95)
* Add test for fetchCourse

* Add tests for fetchDatesTab, fetchOutlineTab, fetchSequence and resetDeadlines

* Implement fetch tabs tests

* Add fail test case for fetchSequence

* Add success test for fetchSequence

* Add test for resetDeadlines

* Update test group name

* Add empty tests for courseware and bookmarks

* Fix wrong field in saveSequencePosition thunk

* Add tests for courseware data layer

* Temporary commit

* Split tests after rebase

* Revert "Fix wrong field in saveSequencePosition thunk"

This reverts commit 4394d363c58ad929f81e587ce4da2241528494b5.

* Fix test for position

* Move executeThunk into utils

* Add test for all reducers

* Add expect statements for logs

* Remove redundant snapshot tests and add some specific checks

* Polishing

* Remove redundant checks

* Fix bug in normalizer and update test

* Upgrade @edx/frontend-platform dependency

* Utilize MockAuthService instead of manual auth package mocking

* Update tests after breaking changes in master

* Remove redundant snapshot check
2020-07-09 10:39:37 -04:00
David Joy
5332be8e65 Update README.rst 2020-07-09 10:28:57 -04:00
Kyle McCormick
e04f588d1f Treat html-type XBlocks as units (#104)
Fixes bug where courses with html-type XBlocks as children
of sequences would ignore those children instead of treating them
as units. This caused the app to later just give up and redirect
to the course home in the old experience.

Also, handle that scenario where we have sections/sequences
with children of unexpected block types more gracefully by
logging an error instead of crashing.

TNL-7305
2020-07-09 10:22:30 -04:00
daphneli-chen
fe013f57c5 AA-203: Created progress page and receiving username, email, and enrollment status (#96)
Co-authored-by: Daphne Li-Chen <dli-chen@edx.org>
2020-07-08 16:00:25 -04:00
David Joy
7df50264cf Update 0004-model-store.md (#101)
Explaining the rationale for model-store in a bit more detail.
2020-07-02 13:24:25 -04:00
David Joy
73c74119f0 Organizationing (#102)
* Moving model-store into “generic” sub-directory.

Also adding a README.md to explain what belongs in “generic”

* Moving user-messages into “generic” sub-directory.

* Moving PageLoading into “generic” sub-directory.

* Moving “tabs” module into “generic” sub-directory.

* Moving InstructorToolbar and MasqueradeWidget up to instructor-toolbar.

The masquerade widget is a sub-module of instructor-toolbar.

* Co-locating celebration APIs with celebration utils.

Also adding an ADR about thunk/API naming conventions and making some other areas of the code adhere to it.

* Moving courseware data (thunks, api) into the courseware module.

Note that cousre-home/data/api still uses normalizeBlocks - this should be fixed so it’s not reaching across.  Maybe we pull that particular API up top.

This PR includes a few TODOs for things I saw, as well as a tiny bit of whitespace cleanup.
2020-07-02 13:11:50 -04:00
Carla Duarte
a6edc9132f AA-186: Refactoring to separate Course Home logic from Courseware (#93)
- Pulled Course Home specific components into `course-home`
- Created a courseHome reducer (and all necessary data files - api, thunks, slice)
- Removed Course Home logic from Courseware's data files (api, thunks, slice, etc.)
- Renamed Outline Tab URL to end in `/home` rather than `/outline` again (per Product)

Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-06-25 10:26:47 -04:00
Patrick Cockwell
8b34f8c792 TNL-7126 BD-29 Visual Updates to Learning Sequence (#88)
* BB-2569: Use faVideo instead of faFilm for video units; Set page title based on section, sequence, and course titles

* Add CourseLicense component with styling

* Reorder the pageTitleBreadCrumbs that are used for setting the page title

* Revert "Add CourseLicense component with styling"

This reverts commit 8d154998de.

* Fix package-lock.json so that only new changes for react-helmet are included
2020-06-23 16:09:12 -04:00
daphneli-chen
534b9b205f AA-122: Created Dates Widget on course home page (#82)
Including upcoming dates and a link to dates tab. Gives user ability to
look at any important upcoming dates for their course and to navigate
to upcoming assignments.

Co-authored-by: Daphne Li-Chen <daphneli-chen@MacBook-Pro.local>
2020-06-23 14:05:18 -04:00
Michael Terry
1471abe7dd dates: fix missing Today entry if it would be last entry (#94) 2020-06-23 09:39:54 -04:00
Michael Terry
d55c8b134c Don't show verified-only badge on Today (#92)
If the Today date entry on the dates tab doesn't have any items,
we were showing the Verified Only badge. This fixes that mistake.
2020-06-22 14:37:03 -04:00
Michael Terry
6c052a2661 Avoid redundant paragon sass import (#91)
Move all scss imports into the main index.scss. This will let them
all know about existing sass variables and avoid duplicate imports.
2020-06-22 11:24:15 -04:00
Michael Terry
ac47454b14 Don't show celebration modal too much (#89)
Make sure we only show it for the first unit of a sequence.
2020-06-19 14:29:13 -04:00
stvn
a18adc4112 Merge PR #71 add/masquerade
* Commits:
  Show audit content lock when masquerading
  Create masquerade widget component
2020-06-19 09:59:38 -07:00
Michael Terry
6cdd075243 AA-137: Add first-section celebration (#78)
When a learner completes their first section in a course, throw up
a modal that celebrates that fact and encourages them to share
progress.
2020-06-18 09:27:11 -04:00
David Joy
eb8c97ee86 fix: Ensure lmsWebUrl is loaded in useExamRedirect (#87)
The sequence.lmsWebUrl variable is loaded as part of the course blocks API.  The status of that API’s request is stored in courseStatus.

The useEffect hook in useExamRedirect didn’t ensure that courseStatus was equal to “loaded”.  This meant that if the sequence loaded first, it might attempt to redirect to sequence.lmsWebUrl even though that variable is still undefined.

When global.location.assign() is given `undefined` as a value, it tacks it onto the end of the URL and calls it a day.  After that, we’ve got a badly formed URL.
2020-06-17 14:07:14 -04:00
stvn
24051232af Show audit content lock when masquerading 2020-06-16 23:59:30 -07:00
stvn
dee5128448 Create masquerade widget component
on the Staff Instructor Toolbar
2020-06-16 23:59:30 -07:00
Nick
5ffc1bc599 Add logging back to the coursemetadata call (#86) 2020-06-16 14:42:18 -04:00
Nick
bf0d3b1565 AA-133 mfe dates banner fix (#85)
- mfe dates banner along with fixing previously reverted
  PR by adding back in original coursemetadata call.

This reverts commit 8df4654cf1.
2020-06-16 13:11:50 -04:00
Nick
8df4654cf1 Revert "AA-133 mfe dates banner (#79)" (#84)
This reverts commit 973f3d68aa.
2020-06-16 10:14:30 -04:00
Nick
973f3d68aa AA-133 mfe dates banner (#79)
- dates banner on dates tab
2020-06-16 09:55:04 -04:00
Carla Duarte
b51809fa50 Quick fix: Corrected Outline Tab redirect URL (#83)
Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-06-15 17:54:16 -04:00
Carla Duarte
253836fa9f AA-181: Outline Tab Refactor (#80)
- Updated the Outline Tab to fetch course blocks from the Outline API.
- Changed naming conventions to more accurately portray the tab naming scheme
(ex. Outline Tab, Dates Tab, etc.)
- Removed logic from `fetchCourses` that was specific to the Outline Tab
2020-06-15 15:19:13 -04:00
David Joy
65173e9f93 fix: remove conditions for an infinite React hooks loop (#81)
The useAlert hook was being given a new payload object every time it was called, defeating any memoization happening inside.

It was also re-calling it’s useEffect hook when alertId changed, which it was changing itself. That’s a no-no.
2020-06-10 17:08:07 -04:00
David Joy
a8d01c423d Miscellaneous small refactorings (#74)
* Normalizing “courseInfo” back into “course”

Splitting it out denormalizes the data and introduces potential data inconsistencies.

* Name component JSX files with the name of the component.

* Normalizing some module exports/naming.

* Moving alerts into a sub-directory.

* DRYing up alert hook creation into reusable useAlert hook.

* Adding some comments about ‘courses’ hydration.
2020-06-02 14:15:12 -04:00
Carla Duarte
025f37cd21 AA-120: Course Tools Widget (#73)
Co-authored-by: Carla Duarte <cduarte@edx.org>
2020-06-02 13:41:23 -04:00
Michael Terry
964bde180a Further UI tweaks to the Dates tab (#76)
Allow for per-item badges if not all items in a given day match
for the badge. And some minor spacing changes.
2020-06-01 09:45:35 -04:00
Nick
209a64c29b AA-117 mfe dates tab waffle flag (#75)
- redirect back to platform dates tab if waffle flags are not
  enable for the mfe dates tab
2020-05-29 13:48:23 -04:00
Michael Terry
bdb1afe990 Finish up basic dates tab support (#70)
- Drop mock data, call real API instead
- Call course metadata API for general info, not the dates API
- Mark text as translatable
- Add badges and timeline dots, group same-day items

AA-116
2020-05-26 13:15:36 -04:00
Michael Terry
7487d8d32f Add basic dates tab (#62)
This is not the final visuals for the dates tab, but an in-progress
page to base further work on.

AA-116
2020-05-26 13:07:24 -04:00
Michael Terry
e101b41c08 Whoops, add one-liner missing from github in last commit (#72) 2020-05-21 12:03:09 -04:00
Michael Terry
2f01e8a646 Refactor containers to share more code (#61)
Specifically, make sure that the header, footer, and tabs are all
shared code so that they look the same and don't need to be
redefined as we add more tab pages.
2020-05-21 11:56:49 -04:00
stvn
589db9356e Merge PR #65 add/staff-links
* Commits:
  Remove explanatory paragraph
  Add new Studio/insights links to InstructorToolbar
  Add courseId to InstructorToolbar props
  Create new config values for Insights/Studio URLs
  Fix missing definition of unitId in InstructorToolbar.props
  Set NODE_ENV in the test environment
  Fix mismatched test BASE_URL
  Cleanup PORT config
2020-05-15 14:25:10 -07:00
stvn
e2f37ff20e Remove explanatory paragraph 2020-05-15 12:36:25 -07:00
stvn
ab544b5d2b Add new Studio/insights links to InstructorToolbar 2020-05-15 12:36:25 -07:00
stvn
7bef14c329 Add courseId to InstructorToolbar props 2020-05-15 12:36:25 -07:00
stvn
5cb11189a7 Create new config values for Insights/Studio URLs 2020-05-15 12:36:23 -07:00
stvn
fd951fb18a Fix missing definition of unitId in InstructorToolbar.props 2020-05-15 12:05:08 -07:00
stvn
b2fa93af13 Set NODE_ENV in the test environment 2020-05-15 12:04:34 -07:00
stvn
93ccdf829b Fix mismatched test BASE_URL 2020-05-15 12:04:34 -07:00
stvn
65ab77bed3 Cleanup PORT config 2020-05-15 12:04:34 -07:00
stvn
efba1c1f5a Merge PR #67 toggle/course-sock
* Commits:
  Show course-sock only when the API says so
2020-05-15 09:37:16 -07:00
Ned Batchelder
3c53c4af4e Mark this repo for inclusion in Open edX release tagging (#68) 2020-05-15 10:08:07 -04:00
Dave St.Germain
2b27f0774d Resume from last completed unit (#66) 2020-05-14 11:17:35 -04:00
stvn
97f335be62 Show course-sock only when the API says so 2020-05-12 12:20:29 -07:00
stvn
6c7af3817b Merge PR #64 fix/banner/expiration
* Commits:
  Make the two rawHtml alerts look near-identical
2020-05-07 12:07:00 -07:00
stvn
a7932ed730 Make the two rawHtml alerts look near-identical 2020-05-07 11:57:35 -07:00
David Joy
29b234e2f0 Scroll to top when the sequenceId or unitId changes (#63)
* Scroll to top when the sequenceId or unitId changes

* Add a spinner to the unit.
2020-05-06 12:59:17 -04:00
David Joy
a718c67f36 Show message when there are no units in a sequence. (#60)
TNL-7191 - We didn’t fully protect against sequences with no units. The next/previous buttons now check whether there is a unit ID and construct a URL without if one doesn’t exist.  When we load a sequence without units, we now show a message to the user so the page doesn’t look broken.
2020-05-05 09:46:18 -04:00
stvn
cc5e5ecc00 Merge PR #55 refactor-iframe-messages
* Commits:
  Refactor iframe message handler
2020-05-04 21:30:03 -07:00
stvn
7df95378d6 Refactor iframe message handler
TNL-7187
2020-05-04 14:17:39 -07:00
Adam Butterworth
18426dd313 make unitNavigationHandler hook depend on unitId (#59)
This should fix intermittent bugs in checking block completions. Prior we were checking the completion only for the first unit loaded in a given sequence no matter the current unit.
2020-05-04 16:55:16 -04:00
Adam Butterworth
a1eee2d662 Fix IE11 layout issue by setting header flex-basis to auto (#58) 2020-05-04 16:14:20 -04:00
Adam Butterworth
7dfb01a397 Mobile fixes: content tools and verified certificate details (#57)
* Prevent wrapping of show notes button

* text overflow

* Update layout for course sock
2020-05-04 16:06:19 -04:00
David Joy
d58a81bf19 Use layout effect to avoid iframe pausing React lifecycle (#56)
Fixes TNL-7187 - Adds a no-op useLayoutEffect hook to Unit.jsx to prevent the unit iframe from pausing React’s rendering lifecycle.  Very strange bug - see comments in that file for more detail.
2020-05-04 12:37:23 -04:00
stvn
bd0ab5b6c9 Merge PR #54 debug/iframe
* Commits:
  Add temporary logging to iframe message handler
2020-04-30 11:46:04 -07:00
stvn
5185f986df Add temporary logging to iframe message handler 2020-04-30 11:43:57 -07:00
David Joy
d3b22bc879 TNL-7164, Enroll Now button fix, flash messages, and custom message props (#53)
* Adding an index.js file for user-messages.

Importing from the module, not its contents.

* Allowing customProps to be passed though AlertList to Alerts.

* UserMessagesProvider can create flash messages.

A flash message is one that will be displayed on the next reload of the page.  UserMessagesProvider now provides a “addFlash” function.  These messages are stored in localStorage and displayed the next time UserMessagesProvider is mounted, which is generally going to be on the next page refresh.

Once displayed, flash messages are cleared out of localStorage.

* Hooking up Enroll Now button and adding “success” alert.

Success alert is shown as a flash message on next page reload.

* Using ALERT_TYPES constants.
2020-04-30 10:22:44 -04:00
stvn
36526def67 Merge PR #52 upgrade offer banner
* Commits:
  Use new upgrade offer banner
  Add new upgrade offer alert
2020-04-23 10:48:58 -07:00
stvn
c510fe1c1d Use new upgrade offer banner 2020-04-23 10:28:03 -07:00
stvn
ca8afb3294 Add new upgrade offer alert 2020-04-23 10:28:00 -07:00
stvn
1f4e2cd6f5 Merge PR #50 banner/lock-access
* Commits:
  Add audit access locked banner
2020-04-21 12:48:56 -07:00
stvn
6d60584596 Add audit access locked banner
when Content Type Gating, aka Feature Based Enrollment is enabled.
2020-04-21 12:46:11 -07:00
stvn
b20a4ed304 Merge PR #49 banner/access-expiration
* Commits:
  Add warning banner for audit access expiration
2020-04-21 12:43:09 -07:00
stvn
44f535ba1e Add warning banner for audit access expiration
to inform users of deadline and prompt them to upgrade to the verified
access track.
2020-04-21 12:38:11 -07:00
David Joy
5f0774b66d Fixing logout URL in dev and test. 2020-04-21 11:26:10 -04:00
Adam Butterworth
04a8638d00 Reduce min-width of unit buttons in nav (#51)
Makes space for more units before swapping the display to a dropdown.
2020-04-16 10:37:29 -04:00
Adam Butterworth
1cc7dc266b Redirect users when they cannot access content (#48)
TNL-7171, TNL-7172, TNL-7173, TNL-7174: When a user is denied access to load courseware, redirect them to the appropriate location based upon the error code returned. If the error code is unknown they will be redirected to course home.
2020-04-15 12:56:51 -04:00
Adam Butterworth
a852182a00 Support can_load_courseware as legacy boolean and future object (#47) 2020-04-09 16:10:55 -04:00
Dave St.Germain
15c3053e87 Adds notes visibility toggle (#44)
* added notes

* moved around components

* Addressed feedback
2020-04-09 14:46:33 -04:00
David Joy
e2399e30d4 fix: Pull lms_base_url out of vertical data from the blocks API (#46)
TNL-7170

lms_base_url becomes lmsBaseUrl in the app and is then used by the InstructorToolbar to link the user back to the LMS.  If it isn’t present, the toolbar hides itself.  This puts it back.
2020-04-09 11:26:02 -04:00
David Joy
a10e6c2826 Switching the MFE to use the new permissions fields (#43)
- “userHasAccess” becomes “canLoadCourseware”, and is loaded from “can_load_courseware”
- “isStaff” is now loaded from “is_staff” instead of “user_has_staff_access”
2020-04-06 15:32:50 -04:00
David Joy
b4fbd1cf83 fix: “current.” was left over from when the implementation used refs (#42) 2020-04-02 15:58:58 -04:00
Adam Butterworth
37610ab181 Improve access control behavior (#39)
Fixes TNL-7175: Redirect to course home if a user is not unenrolled and the course is private.

- Require authentication to use the app while course blocks api requires it
- Gracefully handle course blocks api request failures allowing app to proceed to it redirection logic

Notable changes:

- selectors related to sequences are more resilient to missing models. In the case the course blocks api returns successfully but empty (in this case of enrolled but course not yet started).
- `fetchCourse` thunk handles failures for fetchCourseMeta and fetchCourseBlocks separately using `Promise.allSettled` instead of `Promise.all`
- `denied` is a new `courseStatus`
- Access denied redirect is done using a component at a new route `redirect/course-home/:courseId`

Now handles cases

- User is unauthenticated > redirect to login
- User is authenticated but not enrolled > redirects to lms course home
- When an enrolled user attempts to access courseware before the course start date they will load the sequence (but unable to load the vertical block). This behavior should be fixed in an update to edx-platform
2020-04-02 15:12:07 -04:00
David Joy
70428228a5 fix: Fix UserMessagesProvider state references (#38)
See details in code, but this causes UserMessagesProvider to always use the most “recent” version of its messages and nextId state when its callbacks are called.
2020-04-01 16:07:58 -04:00
David Joy
1dc069dbbf Adding a separate StaffEnrollmentAlert (#41)
This is a separate component because we have no mechanism for passing context/state into these alerts right now, and I’m not sure it’s worth building.  Easier to just use different codes for different situations.
2020-04-01 15:59:05 -04:00
David Joy
9b72380dea Bumping paragon and fixing an i18n build issue (#40) 2020-04-01 15:42:10 -04:00
David Joy
d59875c45d Only redirect if the user has no access and isn’t staff (#37)
TNL-7129

This adds a third clause to our useAccessDeniedRedirect hook, which makes sure the user doesn’t have staff access (instead of normal, enrolled access) prior to redirecting.

As an aside, this redirection approach - irrespective of this PR - is not great.  The UI mostly renders prior to this redirect happening.  It would be better of this hook returned something that would help prevent the UI from rendering while the redirect is in progress.  As it stands, a redirected user will see a flash of the page content prior to being booted.  Not wonderful.
2020-04-01 10:18:54 -04:00
David Joy
834b922aff Create 0005-components-own-their-loading-state.md 2020-04-01 10:13:59 -04:00
Dave St.Germain
a1776f4366 Added calculator component (#33)
TNL-7138
2020-03-30 11:59:31 -04:00
Adam Butterworth
f8ff2e7860 Add sequence-container (#36) 2020-03-25 15:26:51 -04:00
David Joy
a923f3d8e7 fix: use history.push to preserve history for user navigation (#35)
We continue to use history.replace for building the MFE URL, which we don’t want saved in history.
2020-03-24 11:09:45 -04:00
David Joy
8f4ff79351 Rename courseUsageKey to courseId 2020-03-23 17:26:33 -04:00
David Joy
c8be4c401f Allow loading with no unit, and of sequences with no unitIds. (#34)
This requires some defensive programming here and there to let things load in a minimal state.
2020-03-23 16:40:50 -04:00
David Joy
781508dd03 fix: Sometimes a course won’t have units.
If a sequence has no unitIds, bail on changing the URL.
2020-03-23 14:15:25 -04:00
David Joy
57ca2948eb fix: unit object not always ready when course loaded
If we don’t yet have the course blocks API loaded when the course metadata finishes, `unit` will be undefined still, and `unit.id` will fail.
2020-03-23 11:59:34 -04:00
David Joy
9cbb765f8a Extensive refactor of application data management. (#32)
* Extensive refactor of application data management.

- “course-blocks” and “course-meta” are replaced with “courseware” module.  This obscures the difference between the two from the application itself.
- a generic “model-store” module is used to store all course, section, sequence, and unit data in a normalized way, agnostic to the metadata vs. blocks APIs.
- SequenceContainer has been removed, and it’s work is just done in CourseContainer instead.
- UI components are - in general - more responsible for deciding their own behavior during data loading.  If they want to show a spinner or nothing, it’s up to their discretion.
- The API layer is responsible for normalizing data into a form the app will want to use, prior to putting it into the model store.

* Organizing into some more sub-modules.

- Bookmarks becomes it’s own module.
- SequenceNavigation becomes another one.

* More modularization of data directories.

- Moving model-store up to the top.
- Moving fetchCourse and fetchSequence up to the top-level data directory, since they’re used by both courseware and outline.
- Moving getBlockCompletion and updateSequencePosition into the courseware/data directory, since they pertain to that page.

* Normalizing on using the word “title”

* Using history.replace instead of history.push

This fixes TNL-7125

* Allowing sub-components to use hooks and redux

This reduces the amount of data we need to pass around, and lets us move some complexity to more natural modules.

* Fixing bug where enrollment alert is shown for undefined isEnrolled

The enrollment alert would inadvertently be shown if a user navigated from the outline to the course.  This was because it interpreted an undefined “isEnrolled” flag as false.  Instead, we should wait for the isEnrolled flag to be explicitly true or false.

* Organizing modules.

- Renaming “outline” to “course-home”.
- Moving sequence and sequence-navigation modules under the course module.

* Some final application organization and ADR write-ups.

* Final refactoring

- Favoring passing data by ID and looking it up in the store with useModel.
- Moving headers into course-header directory.

* Updating ADRs.  Splitting model-store information out into its own ADR.
2020-03-23 11:31:09 -04:00
Adam Butterworth
720594a7cf Update index.scss
Fix media query
2020-03-16 14:29:23 -04:00
Adam Butterworth
94d11bc7c2 fix header on desktop (#31) 2020-03-16 14:21:28 -04:00
Adam Butterworth
fb83d881f6 Make header mobile responsive (#30)
TNL-7072
2020-03-13 16:46:35 -04:00
Adam Butterworth
da4711581a Responsive instructor toolbar and progressive disclosure (#29)
TNL-7072
2020-03-13 14:55:48 -04:00
Adam Butterworth
a4c978a303 Mobile responsive sequence navigation (#28)
[TNL-7072] When a sequence navigation would overflow, convert it to a dropdown.
2020-03-13 12:57:08 -04:00
Adam Butterworth
24ca1aa730 Improve sequence padding and containers on mobile (#27)
TNL-7072.
- Refactors some of the css container/content class naming
- Moved UnitNavigation out of the Sequence and into its own component.
- Fixes an issue with course tabs where multi-word titles would wrap text.
2020-03-11 11:43:17 -04:00
Adam Butterworth
a0839f0a63 Mobile layout for breadcrumbs and tabs (#26)
TNL-7072 mobile layout updates. Breadcrumbs truncate section and subsection titles with ellipsis. Tabs that would overflow are tucked under a "more" dropdown.
2020-03-11 09:37:54 -04:00
David Joy
d145c45a3b feat: Only showing instructor toolbar for course staff. (#25) 2020-03-10 09:33:36 -04:00
Adam Butterworth
fcddc2d639 Log api request errors to New Relic and display load error (#24)
TNL-7114 Logs any failed api request to New Relic. Show error in CourseContainer instead of a spinner when a course fails to load.
2020-03-09 12:43:02 -04:00
David Joy
6082ade9e0 Disabling next/previous buttons at course beginning and end (#23)
At the end, the “next” button at the bottom of the page is replaced with a friendly message.

This PR also alphabetizes some props for SequenceNavigation and Sequence, as I was adding two new ones - isFirst and isLast.
2020-03-06 16:38:47 -05:00
stvn
8358a2589e Merge PR #15 analytics/sequence-events
* Commits:
  Add logging events for sequence navigation
2020-03-06 16:16:47 -05:00
David Joy
6ba8929c97 Initial version of Course Home page (#20)
* refactor: Moving PageLoading up to the top

This way it can be used on both the courseware and outline pages.

* Adding index.js files to data directories, and PropTypes data shapes

- The course-blocks and course-meta data directories now have index files so their exports can be imported from that, rather than reaching into specific files in the directories.
- Also added “shapes” for use in React Components that use PropTypes for the course blocks data structure, and the course metadata data structure.

* Simplifying/refactoring CourseContainer rendering a bit.

* Adding course outline page.

This page is not complete.

- It contains the ‘outline’ itself with links to the Sequences in the course.
- It contains a very basic stab at displaying dates - they’re not even formatted.
- It shows logistration and enrollment alerts for anonymous and unenrolled users.

It does not include any other content in the right-hand sidebar.  It also doesn’t include a welcome message, or perhaps any number of other features on the page.  This is effectively an initial implementation for discovering how much data we’re missing from our APIs.  It should not be used as-is by any means.
2020-03-06 13:21:18 -05:00
stvn
39a0e50745 Add logging events for sequence navigation
to mirror existing behavior in `edx-platform` [2][3][4].

This adds support for the following events:
- `edx.ui.lms.sequence.next_selected`
- `edx.ui.lms.sequence.previous_selected`
- `edx.ui.lms.sequence.tab_selected`

** Outstanding Events
- `edx.ui.lms.outline.*`
- `edx.ui.lms.link_clicked`

** References
- [1] https://openedx.atlassian.net/browse/TNL-7066
- [2] https://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#course-navigation-events
- [3] https://github.com/edx/edx-platform/blob/master/common/lib/xmodule/xmodule/js/src/sequence/display.js#L315-321
- [4] https://github.com/edx/edx-platform/blob/master/common/lib/xmodule/xmodule/js/src/sequence/display.js#L373-378
2020-03-06 13:00:16 -05:00
Adam Butterworth
5a3597ac4b Remove edX from preview message 2020-03-06 12:18:46 -05:00
Adam Butterworth
ca15a0af7f Add preview message for instructors (#21)
TNL-7106
2020-03-06 12:10:50 -05:00
Adam Butterworth
8347a66375 Update minor style and visual loading (#19) 2020-03-06 09:20:34 -05:00
Adam Butterworth
31dd6b81b8 Minor style update (#18)
Basic design changes described in TNL-7105
2020-03-05 14:36:13 -05:00
David Joy
1ca797f6e8 fix: removing unused/duplicate functions from course-meta/api.js
The two functions removed here are duplicated (and actually used from) course-blocks/api.js.  Think it was just a refactoring oversight from a few weeks ago.
2020-03-05 10:52:33 -05:00
David Joy
f3e559ad9d Adding a TODO about removing the course links route. 2020-03-05 10:40:09 -05:00
David Joy
c3d0ac1417 Custom alerts for anonymous and unenrolled users. (#17)
* build: bumping version of frontend-platform

We’re going to need to use the new getLoginRedirectUrl helper.

* Adding custom alerts for anonymous and unenrolled users.

- Anonymous users are prompted to sign in or register.
- Unenrolled users are prompted to enroll.

The alerts themselves are lazy-loaded as necessary, like the ContentLock component.

This PR also adds `customAlerts` to the AlertList, allowing an application to specify custom components to be shown as Alerts for a given alert code.

* refactor: Renaming enrollmentIsActive to isEnrolled

As per review feedback that the former wasn’t clear.
2020-03-05 10:23:47 -05:00
Adam Butterworth
bda738c9d1 Add next and previous buttons below unit (#16) 2020-03-04 15:20:11 -05:00
Dave St.Germain
7824f58777 Added a verified upgrade component (#14)
* Added a verified upgrade component

* Internationalize course sock
2020-03-04 13:06:09 -05:00
David Joy
4c09d49532 Adding userHasAccess to redux so we can use it in our components. 2020-02-28 13:54:35 -05:00
David Joy
2f90b78814 If the user does not have access to the course, then redirect to the course outline. (#13)
* If the user does not have access to the course, then redirect to the course outline.

In a subsequent PR, if this API call is made on the course outline page in the MFE, we’ll need to be able to prevent the redirect.  But that view of the MFE doesn’t exist yet.

* Moving course outline redirect logic into CourseContainer.

This way, depending on the page calling fetchCourseMetadata, we can make an intelligent choice about whether we want to redirect, show a message, etc.  By redirecting in the API call handler, then we took that choice away from ourselves.
2020-02-28 12:25:03 -05:00
Adam Butterworth
ba6764de43 feat: add link to unit in existing experience (#12)
* feat: add instructor toolbar with link to lms

* fix: right align user account menu dropdown
2020-02-14 13:05:25 -05:00
Adam Butterworth
46cd511e15 feat: add bookmarking for units (#11)
* feat: add bookmarking for units

* refactor: add redux for state management
2020-02-14 09:10:43 -07:00
David Joy
ab3d3e8834 fix: updating URL when unit changes. 2020-02-04 15:00:21 -05:00
David Joy
ec7166ad5d fix: lint both js and jsx files.
Updating jsx to pass linter, too.
2020-02-04 14:58:19 -05:00
David Joy
ee4908565f Updating to use frontend-build 3.0.0. 2020-02-04 14:38:19 -05:00
David Joy
437f50b261 Fixing linting errors. 2020-01-30 18:14:47 -05:00
Dave St.Germain
dc2971870f Use the new API urls (#10) 2020-01-29 12:50:28 -05:00
Adam Butterworth
5d4d196e0b fix: make iframe width better match parent page (#9) 2020-01-29 12:45:44 -05:00
David Joy
304850b7d1 Listen for iframe resize messages from vertical xblock. 2020-01-27 17:01:40 -05:00
Adam Butterworth
3afac3bcdc Allow fullscreen on unit iframe 2020-01-23 17:23:40 -05:00
David Joy
cf4d63ac99 Removing stub “resume your last course” link for now.
Will be added in TNL-7062
2020-01-17 13:00:52 -05:00
David Joy
637af82873 Passing org, course number, and course name to the CourseHeader. 2020-01-17 11:23:27 -05:00
David Joy
740e22e4c8 Getting course tabs working. 2020-01-16 17:05:03 -05:00
David Joy
83f69dcbfc Connect to courses endpoint and print out the result. 2020-01-16 16:30:49 -05:00
David Joy
3cf204fad3 Bit of styling and propTypes 2020-01-16 15:11:14 -05:00
David Joy
e104674bd1 Adding some missing propTypes 2020-01-16 10:45:33 -05:00
David Joy
869eb9da38 Fixing disabled-when-active unit button. 2020-01-16 10:35:30 -05:00
David Joy
501500f116 Adding hard-coded application header. 2020-01-15 15:51:18 -05:00
David Joy
ed2a14de95 Sequence level messaging works from bannerText. 2020-01-15 14:10:14 -05:00
David Joy
d36b5bd0b0 Adding user-messages module and implementing course-level messaging. 2020-01-15 13:59:05 -05:00
David Joy
2fba819c34 Assume a position of 0 if position is null. 2020-01-15 12:44:30 -05:00
David Joy
c48d2ab9a2 Getting ready to deploy to staging.
Adding a staging course link.
Fixing some errors that occurred when using the demo/staging course links.
2020-01-15 12:29:07 -05:00
David Joy
a19903c0b1 Route and breadcrumb cleanup. 2020-01-15 12:00:29 -05:00
David Joy
9d9b65ceb9 Cleaning up old implementation code. 2020-01-15 11:08:01 -05:00
David Joy
41ab9fc68e Fixing breadcrumbs so they contain the unit. 2020-01-14 17:21:48 -05:00
David Joy
0b171ac9f9 Adding the exam redirect back in. 2020-01-14 17:18:03 -05:00
David Joy
89830af45a Refactoring to use containers and components 2020-01-14 17:11:03 -05:00
497 changed files with 65944 additions and 17290 deletions

62
.env
View File

@@ -1,16 +1,48 @@
# See README.rst for explanations of these.
# If you add a new learning MFE-specific variable, please note it there!
NODE_ENV='production'
ACCESS_TOKEN_COOKIE_NAME=null
BASE_URL=null
CREDENTIALS_BASE_URL=null
CSRF_TOKEN_API_PATH=null
ECOMMERCE_BASE_URL=null
LANGUAGE_PREFERENCE_COOKIE_NAME=null
LMS_BASE_URL=null
LOGIN_URL=null
LOGOUT_URL=null
MARKETING_SITE_BASE_URL=null
ORDER_HISTORY_URL=null
REFRESH_ACCESS_TOKEN_ENDPOINT=null
SEGMENT_KEY=null
SITE_NAME=null
USER_INFO_COOKIE_NAME=null
ACCESS_TOKEN_COOKIE_NAME=''
BASE_URL=''
CONTACT_URL=''
CREDENTIALS_BASE_URL=''
CREDIT_HELP_LINK_URL=''
CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL=''
DISCUSSIONS_MFE_BASE_URL=''
ECOMMERCE_BASE_URL=''
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
EXAMS_BASE_URL=''
FAVICON_URL=''
IGNORED_ERROR_REGEX=''
INSIGHTS_BASE_URL=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
LMS_BASE_URL=''
LOGIN_URL=''
LOGOUT_URL=''
LOGO_URL=''
LOGO_TRADEMARK_URL=''
LOGO_WHITE_URL=''
LEGACY_THEME_NAME=''
MARKETING_SITE_BASE_URL=''
ORDER_HISTORY_URL=''
PROCTORED_EXAM_FAQ_URL=''
PROCTORED_EXAM_RULES_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT=''
SEARCH_CATALOG_URL=''
SEGMENT_KEY=''
SESSION_COOKIE_DOMAIN=''
SITE_NAME=''
SOCIAL_UTM_MILESTONE_CAMPAIGN=''
STUDIO_BASE_URL=''
SUPPORT_URL=''
SUPPORT_URL_CALCULATOR_MATH=''
SUPPORT_URL_ID_VERIFICATION=''
SUPPORT_URL_VERIFIED_CERTIFICATE=''
TERMS_OF_SERVICE_URL=''
TWITTER_HASHTAG=''
TWITTER_URL=''
USER_INFO_COOKIE_NAME=''

View File

@@ -1,17 +1,48 @@
# See README.rst for explanations of these.
# If you add a new learning MFE-specific variable, please note it there!
NODE_ENV='development'
PORT=2000
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='localhost:2000'
BASE_URL='http://localhost:2000'
CONTACT_URL='http://localhost:18000/contact'
CREDENTIALS_BASE_URL='http://localhost:18150'
CREDIT_HELP_LINK_URL='https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_credit_courses.html#keep-track-of-credit-requirements'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL=''
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
IGNORED_ERROR_REGEX=''
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
LEGACY_THEME_NAME=''
MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='localhost:1996/orders'
ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000
PROCTORED_EXAM_FAQ_URL=''
PROCTORED_EXAM_RULES_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=null
SEARCH_CATALOG_URL='http://localhost:18000/courses'
SEGMENT_KEY=''
SITE_NAME='edX'
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
STUDIO_BASE_URL='http://localhost:18010'
SUPPORT_URL='https://support.edx.org'
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
TERMS_OF_SERVICE_URL='https://www.edx.org/edx-terms-service'
TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
SESSION_COOKIE_DOMAIN='localhost'

View File

@@ -1,15 +1,47 @@
# See README.rst for explanations of these.
# If you add a new learning MFE-specific variable, please note it there!
NODE_ENV='test'
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='localhost:1995'
BASE_URL='http://localhost:2000'
CONTACT_URL='http://localhost:18000/contact'
CREDENTIALS_BASE_URL='http://localhost:18150'
CREDIT_HELP_LINK_URL='https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_credit_courses.html#keep-track-of-credit-requirements'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381'
DISCUSSIONS_MFE_BASE_URL='http://localhost:2002'
ECOMMERCE_BASE_URL='http://localhost:18130'
ENABLE_JUMPNAV='true'
ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
EXAMS_BASE_URL='http://localhost:18740'
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
IGNORED_ERROR_REGEX=''
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
LEGACY_THEME_NAME=''
MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='localhost:1996/orders'
ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000
PROCTORED_EXAM_FAQ_URL=''
PROCTORED_EXAM_RULES_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEGMENT_KEY=null
SEARCH_CATALOG_URL='http://localhost:18000/courses'
SEGMENT_KEY=''
SITE_NAME='edX'
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
STUDIO_BASE_URL='http://localhost:18010'
SUPPORT_URL='https://support.edx.org'
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
TERMS_OF_SERVICE_URL='https://www.edx.org/edx-terms-service'
TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'

View File

@@ -1,4 +1,5 @@
coverage/*
dist/
packages/
node_modules/
jest.config.js
jest.config.js

View File

@@ -1,3 +1,17 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('eslint');
const config = createConfig('eslint', {
rules: {
// TODO: all these rules should be renabled/addressed. temporarily turned off to unblock a release.
'react-hooks/rules-of-hooks': 'off',
'react-hooks/exhaustive-deps': 'off',
'import/no-extraneous-dependencies': 'off',
'no-restricted-exports': 'off',
'react/jsx-no-useless-fragment': 'off',
'react/no-unknown-property': 'off',
'func-names': 'off',
},
});
module.exports = config;

View File

@@ -0,0 +1,19 @@
# Run the workflow that adds new tickets that are either:
# - labelled "DEPR"
# - title starts with "[DEPR]"
# - body starts with "Proposal Date" (this is the first template field)
# to the org-wide DEPR project board
name: Add newly created DEPR issues to the DEPR project board
on:
issues:
types: [opened]
jobs:
routeissue:
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}

View File

@@ -0,0 +1,20 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "label: " it tries to apply
# the label indicated in rest of comment.
# If the comment starts with "remove label: ", it tries
# to remove the indicated label.
# Note: Labels are allowed to have spaces and this script does
# not parse spaces (as often a space is legitimate), so the command
# "label: really long lots of words label" will apply the
# label "really long lots of words label"
name: Allows for the adding and removing of labels via comment
on:
issue_comment:
types: [created]
jobs:
add_remove_labels:
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master

10
.github/workflows/commitlint.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# Run commitlint on the commit messages in a pull request.
name: Lint Commit Messages
on:
- pull_request
jobs:
commitlint:
uses: openedx/.github/.github/workflows/commitlint.yml@master

View File

@@ -0,0 +1,13 @@
#check package-lock file version
name: Lockfile Version check
on:
push:
branches:
- master
pull_request:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

12
.github/workflows/self-assign-issue.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "assign me" it assigns the author to the
# ticket (case insensitive)
name: Assign comment author to ticket if they say "assign me"
on:
issue_comment:
types: [created]
jobs:
self_assign_by_comment:
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master

View File

@@ -0,0 +1,12 @@
name: Update Browserslist DB
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
jobs:
update-browserslist:
uses: openedx/.github/.github/workflows/update-browserslist-db.yml@master
secrets:
requirements_bot_github_token: ${{ secrets.requirements_bot_github_token }}

23
.github/workflows/validate.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: validate
on:
push:
branches:
- master
pull_request:
branches:
- '**'
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Nodejs Env
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VER }}
- run: make validate.ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true

11
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.DS_Store
.eslintcache
.idea
*.swp
*.swo
node_modules
npm-debug.log
coverage
@@ -8,11 +10,18 @@ coverage
dist/
src/i18n/transifex_input.json
temp/babel-plugin-react-intl
logs
### pyenv ###
.python-version
### Emacs ###
### Editors ###
*~
/temp
/.vscode
# Local package dependencies
module.config.js
# Local environment overrides
.env.private

1
.husky/_/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*

31
.husky/_/husky.sh Normal file
View File

@@ -0,0 +1,31 @@
#!/bin/sh
if [ -z "$husky_skip_init" ]; then
debug () {
if [ "$HUSKY_DEBUG" = "1" ]; then
echo "husky (debug) - $1"
fi
}
readonly hook_name="$(basename "$0")"
debug "starting $hook_name..."
if [ "$HUSKY" = "0" ]; then
debug "HUSKY env variable is set to 0, skipping hook"
exit 0
fi
if [ -f ~/.huskyrc ]; then
debug "sourcing ~/.huskyrc"
. ~/.huskyrc
fi
export readonly husky_skip_init=1
sh -e "$0" "$@"
exitCode="$?"
if [ $exitCode != 0 ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)"
fi
exit $exitCode
fi

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18

View File

@@ -1,15 +0,0 @@
language: node_js
node_js: 12
before_install:
- npm install -g npm@6
install:
- npm ci
script:
- make validate-no-uncommitted-package-lock-changes
- npm run i18n_extract
- npm run lint
- npm run test
- npm run build
- npm run is-es5
after_success:
- codecov

View File

@@ -1,8 +1,9 @@
[main]
host = https://www.transifex.com
[edx-platform.frontend-app-learning]
[o:open-edx:p:edx-platform:r:frontend-app-learning]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON
type = KEYVALUEJSON

0
LICENSE Executable file → Normal file
View File

43
Makefile Executable file → Normal file
View File

@@ -1,11 +1,10 @@
transifex_resource = frontend-app-learning
transifex_langs = "ar,fr,es_419,zh_CN"
export TRANSIFEX_RESOURCE=frontend-app-learning
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fa_IR,fr_CA,it_IT,pt_PT,de_DE"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
@@ -38,17 +37,45 @@ push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -f --mode reviewed --language=$(transifex_langs)
tx pull -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
pull_translations:
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-app-learning/src/i18n/messages:frontend-app-learning
$(intl_imports) paragon frontend-component-header frontend-component-footer frontend-app-learning
endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json
.PHONY: validate
validate:
make validate-no-uncommitted-package-lock-changes
npm run i18n_extract
npm run lint -- --max-warnings 0
npm run test
npm run build
.PHONY: validate.ci
validate.ci:
npm ci
make validate

View File

@@ -1,22 +1,121 @@
|Build Status| |Coveralls| |npm_version| |npm_downloads| |license|
|codecov| |license|
frontend-app-learning
=========================
Please tag **@edx/teaching-and-learning** on any PRs or issues. Thanks.
Introduction
------------
React app for edX learning.
This is the Learning MFE (micro-frontend application), which renders all
learner-facing course pages (like the course outline, the progress page,
actual course content, etc).
Please tag **@edx/engage-squad** on any PRs or issues. Thanks.
.. |codecov| image:: https://codecov.io/gh/edx/frontend-app-learning/branch/master/graph/badge.svg?token=3z7XvuzTq3
:target: https://codecov.io/gh/edx/frontend-app-learning
.. |license| image:: https://img.shields.io/badge/license-AGPL-informational
:target: https://github.com/openedx/frontend-app-account/blob/master/LICENSE
Development
-----------
Start Devstack
^^^^^^^^^^^^^^
To use this application, `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
- Run ``make dev.up.lms``
- Visit http://localhost:2000/course/course-v1:edX+DemoX+Demo_Course to view the demo course. You can replace ``course-v1:edX+DemoX+Demo_Course`` with a different course key.
Local module development
^^^^^^^^^^^^^^^^^^^^^^^^
To develop locally on modules that are installed into this app, you'll need to create a ``module.config.js``
file (which is git-ignored) that defines where to find your local modules, for instance::
module.exports = {
/*
Modules you want to use from local source code. Adding a module here means that when this app
runs its build, it'll resolve the source from peer directories of this app.
moduleName: the name you use to import code from the module.
dir: The relative path to the module's source code.
dist: The sub-directory of the source code where it puts its build artifact. Often "dist", though you
may want to use "src" if the module installs React as a peer/dev dependency.
*/
localModules: [
{ moduleName: '@edx/paragon/scss', dir: '../paragon', dist: 'scss' },
{ moduleName: '@edx/paragon', dir: '../paragon', dist: 'dist' },
{ moduleName: '@edx/frontend-enterprise', dir: '../frontend-enterprise', dist: 'src' },
{ moduleName: '@edx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
],
};
See https://github.com/openedx/frontend-build#local-module-configuration-for-webpack for more details.
Deployment
----------
The Learning MFE is similar to all the other Open edX MFEs. Read the Open
edX Developer Guide's section on
`MFE applications <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html>`_.
Environment Variables
^^^^^^^^^^^^^^^^^^^^^
This MFE is configured via environment variables supplied at build time.
All micro-frontends have a shared set of required environment variables,
as documented in the Open edX Developer Guide under
`Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`_.
The learning micro-frontend also supports the following additional variables:
CREDIT_HELP_LINK_URL
A link to resources to help explain what course credit is and how to earn it.
ENABLE_JUMPNAV
Enables the new Jump Navigation feature in the course breadcrumbs, defaulted to the string 'true'.
Disable to have simple hyperlinks for breadcrumbs. Setting it to any other value but 'true' ('false','I love flags', 'etc' would disable the Jumpnav).
This feature flag is slated to be removed as jumpnav becomes default. Follow the progress of this ticket here:
https://openedx.atlassian.net/browse/TNL-8678
SOCIAL_UTM_MILESTONE_CAMPAIGN
This value is passed as the ``utm_campaign`` parameter for social-share
links when celebrating learning milestones in the course. Optional.
Example: ``milestone``
SUPPORT_URL_CALCULATOR_MATH
A link that explains how to use the in-course calculator. You can use the
one in the example below, if you don't want to have your own branded version.
Example: https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator
SUPPORT_URL_ID_VERIFICATION
A link that explains how to verify your ID. Shown in contexts where you need
to verify yourself to earn a certificate. The example link below is probably too
edx.org-specific to use for your own site.
Example: https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity
SUPPORT_URL_VERIFIED_CERTIFICATE
A link that explains what a verified certificate is. You can use the
one in the example below, if you don't want to have your own branded version.
Optional.
Example: https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate
TWITTER_HASHTAG
This value is used in the Twitter social-share link when celebrating learning
milestones in the course. Will prefill the suggested post with this hashtag.
Optional.
Example: ``brandedhashtag``
TWITTER_URL
A link to your Twitter account. The Twitter social-share link won't appear
unless this is set. Optional.
Example: https://twitter.com/edXOnline
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-learning.svg?branch=master
:target: https://travis-ci.org/edx/frontend-app-learning
.. |Coveralls| image:: https://img.shields.io/coveralls/edx/frontend-app-learning.svg?branch=master
:target: https://coveralls.io/github/edx/frontend-app-learning
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-learning.svg
:target: @edx/frontend-app-learning
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-learning.svg
:target: @edx/frontend-app-learning
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-learning.svg
:target: @edx/frontend-app-learning

View File

@@ -0,0 +1,55 @@
# Courseware Page Decisions
**See [0009-courseware-api-direction.md](0009-courseware-api-direction.md) for updates!**
## Courseware data loading
Today we have strictly hierarchical courses - a course contains sections, which contain sequences, which contain units, which contain components.
In creating the courseware pages of this app, we needed to choose how often we fetch data from the server. If we fetch it once and try to get the whole course, including all the data we need in its entire hierarchy, then the request will take 30+ seconds and be a horrible UX. If we try to fetch too granularly, we risk making hundreds of calls to the LMS, incuring both request overhead and common server-side processing that needs to occur for each of those requests.
Instead, we've chosen to load data via the following:
- The course blocks API (/api/courses/v2/blocks) for getting the overall structure of the course (limited data on the whole hierarchy)
- The course metadata API (/api/courseware/course) for detailed top-level data, such as dates, enrollment status, info for tabs across the top of the page, etc.
- The sequence metadata API (/api/courseware/sequence) for detailed information on a sequence, such as which unit to display, any banner messages, whether or not the sequence has a prerequisite, if it's an exam, etc.
- The xblock endpoint (http://localhost:18000/xblock/:block_id) which renders HTML for an xBlock by ID, used to render Unit contents. This HTML is loaded into the application via an iFrame.
These APIs aren't perfect for our usage, but they're getting the job done for now. They weren't built for our purposes and thus load more information than we strictly need, and aren't as performant as we'd like. Future milestones of the application may rely on new, more performant APIs (possibly BFFs)
## Unit iframing
We determined, as part of our project discovery, that in order to deliver value to users sooner, we would iframe in content of units. This allowed us to avoid rebuilding the UI for unit/component xblocks in the micro-frontend, which is a daunting task. It also allows existing custom xblocks to continue to work for now, as they wouldn't have to be re-written.
A future iteration of the project may go back and pull the unit rendering into the MFE.
## Strictly hierarchical courses
We've also made the assumption that courses are strictly hierarchical - a given section, sequence, or unit doesn't have multiple parents. This is important, as it allows us to navigate the tree in the client in a deterministic way. If we need to find out who the parent section of a sequence is, there's only one answer to that question.
## Determining which sequences and units to show
The courseware URL scheme:
`/course/:courseId(/:sequenceId(/:unitId))`
Sequence ID and unit ID are optional.
Today, if the URL only specifies the course ID, we need to pick a sequence to show. We do this by picking the first sequence of the course (as dictated by the course blocks API) and update the URL to match. _After_ the URL has been updated, the application will attempt to load that sequence.
Similarly, if the URL doesn't contain a unit ID, we use the `position` field of the sequence to determine which unit we want to display from that sequence. If the position isn't specified in the sequence, we choose the first unit of the sequence. After determining which unit to display, we update the URL to match. After the URL is updated, the application will attempt to load that unit via an iFrame.
_This URL scheme has been expanded upon in
[ADR #8: Liberal courseware path handling](./0008-liberal-courseware-path-handling.md)._
## "Container" components vs. display components
This application makes use of a few "container" components at the top level - CoursewareContainer and CourseHomeContainer.
The point of these containers is to introduce a layer of abstraction between the UI representation of the pages and the way their data was loaded, as described above.
We don't want our Course.jsx component to be intimately aware - for example - that it's data is loaded via two separate APIs that are then merged together. That's not useful information - it just needs to know where it's data is and if it's loaded. Furthermore, this layer of abstraction lets us normalize field names between the various APIs to let our MFE code be more consistent and readable. This normalization is done in the src/data/api.js layer.
## Navigation
Course navigation in a hierarchical course happens primarily via the "sequence navigation". This component lets users navigate to the next and previous unit in the course, and also select specific units within the sequence directly. The next and previous buttons (SequenceNavigation and UnitNavigation) delegate decision making up the tree to CoursewareContainer. This is an intentional separation of concerns which should allow different CoursewareContainer-like components to make different decisions about what it means to go to the "next" or "previous" sequence. This is in support of future course types such as "pathway" courses and adaptive learning sequences. There is no actual code written for these course types, but it felt like a good separation of concerns.

View File

@@ -0,0 +1,7 @@
# Course Home Decisions
The course home page is not complete as of this writing.
It was added to the MFE as a proof of concept for the Engagement theme's Always Available squad, as they were intending to do some work in the legacy course home page in the LMS, and we wanted to understand whether it would be more easily done in this application.
It uses the same APIs as the courseware page, for the most part. This may not always be the case, but it is for now. Differing API shapes may be faster for both pages.

View File

@@ -0,0 +1,9 @@
## Model Store
Because we have a variety of models in this app (course, section, sequence, unit), we use a set of generic 'model store' reducers in redux to manage this data. Once loaded from the APIs, the data is put into the model store by type and by ID, which allows us to quickly access it in the application. Furthermore, any sub-trees of model children (like "items" in the sequence metadata API) are flattened out and stored by ID in the model-store, and their arrays replaced by arrays of IDs. This is a recommended way to store data in redux as documented here:
https://redux.js.org/faq/organizing-state#how-do-i-organize-nested-or-duplicate-data-in-my-state
Different modules of the application maintain individual/lists of IDs that reference data stored in the model store. These are akin to indices in a database, in that they allow you to quickly extract data from the model store without iteration or filtering.
A common pattern when loading data from an API endpoint is to use the model-store's redux actions (addModel, updateModel, etc.) to load the "models" themselves into the model store by ID, and then dispatch another action to save references elsewhere in the redux store to the data that was just added. When adding courses, sequences, etc., to model-store, we also save the courseId and sequenceId in the 'courseware' part of redux. This means the courseware React Components can extract the data from the model-store quickly by using the courseId as a key: `state.models.courses[state.courseware.courseId]`. For an array, it iterates once over the ID list in order to extract the models from model-store. This iteration is done when React components' re-render, and can be done less often through memoization as necessary.

View File

@@ -0,0 +1,17 @@
# Components Own Their Own Loading State
Currently, the majority of the components in the component tree for both Courseware and CourseHome own their own loading state. This means that they're _aware_ of the loading status (loading, loaded, failed) of the resources they depend on, and are expected to adjust their own rendering based on that state.
The alternative is for a given component's parent to be responsible for this logic. Under normal circumstances, if the parents were responsible, it would probably result in simpler code in general. A component could just take for granted that if it's being rendered, all it's data must be ready.
*We think that that approach (giving the parents responsibility) isn't appropriate for this application.*
We expect - in the longer term - that different courses/course staff may switch out component implementations. Use a different form of SequenceNavigation, for instance. Because of this, we didn't want parent components to be too aware of the nature of their children. The children are more self-contained this way, though we sacrifice some simplicity for it.
If, for instance, the Sequence component renders a skeleton of the SequenceNavigation, the look of that skeleton is going to be based on an understanding of how the SequenceNavigation renders itself. If the SequenceNavigation implementation is switched out, that loading code in the Sequence may be wrong/misleading to the user. If we leave the loading logic in the parent, we then have to branch it for all the types of SequenceNavigations that may exist - this violates the Open/Closed principle by forcing us to update our application when we try to make a new extension/implementation of a sub-component (assuming we have a plugin/extension/component replacement framework in place).
By moving the loading logic into the components themselves, the idea is to allow a given component to render as much of itself as it reasonably can - this may mean just a spinner, or it may mean a "skeleton" UI while the resources are loading. The parent doesn't need to be aware of the details.
## Under what circumstances would we reverse this decision?
If we find, in time, that we aren't seeing that "switching out component implementations" is a thing that's happening, then we can probably simplify the application code by giving parents the responsibility of deciding when to render their children, rather than keeping that responsibility with the children themselves.

View File

@@ -0,0 +1,24 @@
# Naming API functions and redux thunks
Because API functions and redux thunks are two parts of a larger process, we've informally settled on some naming conventions for them to help differentiate the type of code we're looking at.
## API Functions
This micro-frontend follows a pattern of naming API functions with a prefix for their HTTP verb.
Examples:
`getCourseBlocks` - The GET request we make to load course blocks data.
`postSequencePosition` - The POST request for saving sequence position.
## Redux Thunks
Meanwhile, we use a different set of verbs for redux thunks to differentiate them from the API functions. For instance, we use the `fetch` prefix for loading data (primarily via GET requests), and `save` for sending data back to the server (primarily via POST or PATCH requests)
Examples:
`fetchCourse` - The thunk for getting course data across several APIs.
`fetchSequence` - The thunk for the process of retrieving sequence data.
`saveSequencePosition` - Wraps the POST request for sending sequence position back to the server.
The verb prefixes for thunks aren't perfect - but they're a little more 'friendly' and semantically meaningful than the HTTP verbs used for APIs. So far we have `fetch`, `save`, `check`, `reset`, etc.

View File

@@ -0,0 +1,66 @@
# Testing
## Status
Draft
Let's live with this a bit longer before deciding it's a solid approach and marking this Approved.
## Context
We'd like to all be on the same page about how to approach testing, what is
worth testing, and how to do it.
## React Testing Library
We'll use react-testing-library and jest as the main testing tools.
This has some implications about how to test. You can read the React Testing Library's
[Guiding Principles](https://testing-library.com/docs/guiding-principles), but the main
takeaway is that you should be interacting with React as closely as possible to the way
the user will interact with it.
For example, they discourage using class or element name selectors to find components
during a test. Instead, you should find them by user-oriented attributes like labels,
text, or roles. As a last resort, by a `data-testid` tag.
## Mocking data
We'll use [Rosie](https://github.com/rosiejs/rosie) as a tool for building JavaScript objects.
Our main use case for Rosie is to use factories in order to mock the data we'd like to fetch when rendering components.
[axios-mock-adapter](https://www.npmjs.com/package/axios-mock-adapter) allows us to mock the response of an HTTP request.
For example, we may use a factory to build a course metadata object:
`const courseMetadata = Factory.build('courseMetadata');`
Then we'd pass that `courseMetadata` object into an axios mock call:
`axiosMock.onGet('example.com').reply(200, courseMetadata);`
This way, when a component sends a GET request to `example.com` within the test's lifecycle, the request will be intercepted
by the axios-mock-adapter, and the courseMetadata object will be returned.
These factories should live within the data directories they intend to mock
```
courseware
| data
| __factories__
| courseMetadata.factory.js /* used to define the Rosie factory */
| api.js /* getCourseMetadata() lives here */
```
## What to Test
We have not found exhaustive unit testing of frontend code to be worth the trouble.
Rather, let's focus on testing non-obvious behavior.
In essence: `test behavior that wouldn't present itself to a developer playing around`.
Practically speaking, this means error states, interactive components, corner cases,
or anything that wouldn't come up in a demo course. Something a developer wouldn't
notice in the normal course of working in devstack.
## Snapshots
In practice, we've found snapshots of component trees to be too brittle to be worth it,
as refactors occur or external libraries change.
They can still be useful for data (like redux tests) or tiny isolated components.
But please avoid for any "interesting" component. Prefer inspecting the explicit behavior
under test, rather than just snapshotting the entire component tree.

View File

@@ -0,0 +1,90 @@
# Liberal courseware path handling
## Status
Accepted
_This updates some of the content in [ADR #2: Courseware page decisions](./0002-courseware-page-decisions.md)._
## Context
The courseware container currently accepts three path forms:
1. `/course/:courseId`
2. `/course/:courseId/:sequenceId`
3. `/course/:courseId/:sequenceId/:unitId`
Forms #1 and #2 are always redirected to Form #3 via simple set of rules:
* If the sequenceId is not specified, choose the first sequence in the course.
* If the unitId is not specified, choose the active unit in the sequence,
or the first unit if none are active.
Thus, Form #3 is effectively the canonoical path;
all Learning MFE units should be served from it.
We acknowledge that the best user experience is to link directly to the canonoical
path when possible, since it skips the redirection steps.
Still, there are times when it is necessary or prudent to link just to a course or
a sequence.
Through recent work in the LMS, we are realizing that there are _also_ times where it
would be simpler or more performant to link a user to an
_entire section without specifying a squence_ or to a
_unit without including the sequence_.
Specifically, this capability would let as avoid further modulestore or
block transformer queries in order to discern the course structure when trying to
direct a learner to a section or unit.
Futhermore, we hypothesize that being able to build a Learning MFE courseware link
with just a unit ID or a section ID will be a nice simplifying quality for future
development or debugging.
## Decision
The courseware container will accept five total path forms:
1. `/course/:courseId`
2. `/course/:courseId/:sectionId`
3. `/course/:courseId/:sectionId/:unitId`
4. `/course/:courseId/:sequenceId`
5. `/course/:courseId/:unitId`
6. `/course/:courseId/:sequenceId/:unitId`
The redirection rules are as follows:
* Forms #1 redirects to Form #4 by selecting the first sequence in the course.
* Form #2 redirects to Form #4 by selecting to the first sequence in the section.
* Form #3 redirects to Form #5 by dropping the section ID.
* Form #4 redirects to Form #6 by choosing the active unit in the sequence
(or the first unit, if none are active).
* Form #5 redirects to Form #6 by filling in the ID of the sequence that the
specified unit belongs to (in the edge case where the unit belongs to multiple
sequences, the first sequence is selected).
As before, Form #5 is the canonocial courseware path, which is always redirected to
by any of the other courseware path forms.
## Consequences
The above decision is implemented.
## Further work
At some point, we may decide to further extend the URL scheme to be
more human-readable.
We can't make UsageKeys themselves more readable because they're tied to student state,
but we could introduce a new optional `slug` field on Sequences,
which would be captured and propagated to the learning_sequences API.
We could eventually do something similar to Units, since those slugs only have to be sequence-local.
So eventually, URLs could look less like:
```
https://learning.edx.org/course/course-v1:edX+DemoX.1+2T2019/block-v1:edX+DemoX.1+2T2019+type@sequential+block@e0a61b3d5a2046949e76d12cac5df493/block-v1:edX+DemoX.1+2T2019+type@vertical+block@52dbad5a28e140f291a476f92ce11996
```
And more like:
```
https://learning.edx.org/course/course-v1:edX+DemoX.1+2T2019/Being_Social/Teams
```

View File

@@ -0,0 +1,62 @@
# Direction of Courseware APIs
In order to allow for greater flexibility and separation of concerns, we're going to stop using the Course Blocks API for navigational data, and pull that data from the Learning Sequences Outlines API instead. The intention is to give us four distinct layers of courseware that can eventually be composed in different ways:
* Learning Context Metadata
* Learning Context Navigation
* Sequence Navigation
* Unit Rendering
Note that "Learning Context" is a generalization of "Course" that includes other things like Content Libraries, Learning Pathways, and potentially other logical groupings of content.
This is a refinement of [0002-courseware-page-decisions.md](0002-courseware-page-decisions.md). The fundamental layers remain the same, but this document tries to better clarify the boundaries and path forward for these layers. We're not making these layers completely swappable/pluggable now, but we should separate the data access in a way that allows for that in the future.
## Background
We currently make four primary requests to the LMS when rendering courseware instructional content:
1. Course Metadata: `/api/courseware/course/{courseId}` (REST API)
2. Course Blocks API: `/api/courses/v2/blocks/?course_id={courseId}` (REST API)
3. Sequence Metadata: `/api/courseware/sequence/{sequenceUsageKey}` (REST API)
4. Unit: `/xblock/{unitBlockUsageKey}` (rendered in an iframe)
There is a significant amount of overlap between the Course Blocks API and the others at the moment, since Course Blocks takes a static snapshot of the entire tree of course content at once. There are a few problems with the current arrangement:
* It's slow and complex. The Course Blocks API can be difficult to maintain and reason about, and trickier to optimize.
* Assuming that all course structures are the same makes it difficult to support other content types, like LabXchange Learning Pathways or adaptive content.
* The overlap between Course Blocks and the other APIs means that there can be conflicts about the state.
## Motivating Vision
We have seen a desire to extend or enhance the courseware experience in various ways:
Learning Context Navigation
* Allowing for shorter, human-readable URLs in courseware.
* Smaller courses that do not need the current navigational hierarchy.
* LabXchange pathways.
Sequence Navigation
* Adaptive content, where the full list of units is not known up front.
* More limited navigation, where content is pushed linearly, without the ability to jump ahead.
* Different layouts for content browsing.
Unit Rendering
* Use of QTI content (currently serviced by cc2olx conversion).
* Desire to experiment with a next-gen version of XBlock.
* Use of entirely LTI units.
The idea would be to insulate each layer from the layers above and below it. Sequence rendering shouldn't be affected by whether or not it's in a two level hierarchy (Course → Section → Sequence), or a flat one (Course → Sequence). Learning Context Navigation should be able to reference Sequences without caring if a Sequence is an adaptive one or not. Sequences should be able to have a common interface to call Unit iframes, whether those Units are rendering XBlocks or QTI content.
Note that supporting these types of course structures would require downstream changes in other systems as well (e.g. analytics).
## Next Step: Removing use of the Course Blocks API.
The next step in this process is to remove the call to the Course Blocks API, and split its responsibilities across just the existing Learning Sequences Outline and Sequence Metadata APIs. This will involve at least a couple of steps.
### Complete rollout of Learning Sequences Outline calls.
We're currently in a transitional state between these APIs where the Learning Sequences Outline calls are only rolled out on a small handful of courses.
### Shift Sequence and Unit metadata to only come from Sequence Metadata API.
We currently pull this information from both Course Blocks and the Sequence Metadata API. We can consolidate on just the Sequence Metadata API. There is also server side optimization that can be done with the Sequence Metadata API calls as part of this work.

View File

@@ -1,30 +0,0 @@
# Perf test courses
These courses have some large xblocks and small ones. One course has many sequences, the other has fewer.
## Big course: course-v1:MITx+CTL.SC0x+3T2016
- MFE URL: https://learning.edx.org/course/course-v1%3AMITx%2BCTL.SC0x%2B3T2016/0
- URL: https://courses.edx.org/courses/course-v1:MITx+CTL.SC0x+3T2016/course/
### Small xblock
- ID: block-v1:MITx+CTL.SC0x+3T2016+type@vertical+block@0586b59f1cf74e3c982f0b9070e7ad33
- URL: https://courses.edx.org/courses/course-v1:MITx+CTL.SC0x+3T2016/courseware/6a31d02d958e45a398d8a5f1592bdd78/b1ede7bf43c248e19894040718443750/1?activate_block_id=block-v1%3AMITx%2BCTL.SC0x%2B3T2016%2Btype%40vertical%2Bblock%400586b59f1cf74e3c982f0b9070e7ad33
### Big xblock
- ID: block-v1:MITx+CTL.SC0x+3T2016+type@vertical+block@84d6e785f548431a9e82e58d2df4e971
- URL: https://courses.edx.org/courses/course-v1:MITx+CTL.SC0x+3T2016/courseware/b77abc02967e401ca615b23dacf8d115/4913db3e36f14ccd8c98c374b9dae809/2?activate_block_id=block-v1%3AMITx%2BCTL.SC0x%2B3T2016%2Btype%40vertical%2Bblock%4084d6e785f548431a9e82e58d2df4e971
## Small course: course-v1:edX+DevSec101+3T2018
- URL: https://courses.edx.org/courses/course-v1:edX+DevSec101+3T2018/course/
- MFE URL: https://learning.edx.org/course/course-v1%3AedX%2BDevSec101%2B3T2018/0
### Small xblock
- ID: block-v1:edX+DevSec101+3T2018+type@vertical+block@931f96d1822a4fe5b521fcda19245dca
- URL: https://courses.edx.org/courses/course-v1:edX+DevSec101+3T2018/courseware/ee898e64bd174e4aba4c07cd2673e5d3/1a37309647814ab8b333c7a17d50abc4/1?activate_block_id=block-v1%3AedX%2BDevSec101%2B3T2018%2Btype%40vertical%2Bblock%40931f96d1822a4fe5b521fcda19245dca
### Big-ish xblock
- ID: block-v1:edX+DevSec101+3T2018+type@vertical+block@d88210fbc2b74ceab167a52def04e2a0
- URL: https://courses.edx.org/courses/course-v1:edX+DevSec101+3T2018/courseware/b0e2c2b78b5d49308e1454604a255403/38c7049bc8e44d309ab3bdb7f54ae6ae/2?activate_block_id=block-v1%3AedX%2BDevSec101%2B3T2018%2Btype%40vertical%2Bblock%40d88210fbc2b74ceab167a52def04e2a0

View File

@@ -1,11 +1,14 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('jest', {
setupFiles: [
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
],
coveragePathIgnorePatterns: [
'src/setupTest.js',
'src/i18n',
'src/.*\\.exp\\..*',
],
testTimeout: 30000,
testEnvironment: 'jsdom'
});

View File

@@ -3,3 +3,8 @@
oeps: {}
owner: edx/platform-core-tnl
openedx-release:
# The openedx-release key is described in OEP-10:
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html
# The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ
ref: master

40864
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,62 +4,78 @@
"description": "Frontend learning application.",
"repository": {
"type": "git",
"url": "git+https://github.com/edx/frontend-app-learning.git"
"url": "git+https://github.com/openedx/frontend-app-learning.git"
},
"browserslist": [
"last 2 versions",
"ie 11"
"extends @edx/browserslist-config"
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "fedx-scripts eslint",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"prepare": "husky install",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"author": "edX",
"license": "AGPL-3.0",
"homepage": "https://github.com/edx/frontend-app-learning#readme",
"homepage": "https://github.com/openedx/frontend-app-learning#readme",
"publishConfig": {
"access": "public"
},
"bugs": {
"url": "https://github.com/edx/frontend-app-learning/issues"
"url": "https://github.com/openedx/frontend-app-learning/issues"
},
"dependencies": {
"@edx/frontend-component-footer": "^10.0.6",
"@edx/frontend-component-header": "^2.0.3",
"@edx/frontend-platform": "^1.1.11",
"@edx/paragon": "^7.2.0",
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0",
"@fortawesome/free-regular-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@fortawesome/react-fontawesome": "^0.1.8",
"core-js": "^3.6.2",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"redux": "^4.0.4",
"regenerator-runtime": "^0.13.3"
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "12.0.0",
"@edx/frontend-component-header": "4.0.0",
"@edx/frontend-lib-special-exams": "2.19.1",
"@edx/frontend-platform": "4.3.0",
"@edx/paragon": "20.28.4",
"@edx/react-unit-test-utils": "npm:@edx/react-unit-test-utils@1.5.3",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.18",
"@popperjs/core": "2.11.8",
"@reduxjs/toolkit": "1.8.1",
"classnames": "2.3.2",
"core-js": "3.22.2",
"history": "5.3.0",
"js-cookie": "3.0.1",
"lodash.camelcase": "4.3.0",
"prop-types": "15.8.1",
"query-string": "^7.1.3",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-helmet": "6.1.0",
"react-redux": "7.2.9",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"react-share": "4.4.1",
"redux": "4.1.2",
"regenerator-runtime": "0.13.11",
"reselect": "4.1.7",
"truncate-html": "1.0.4",
"util": "0.12.5"
},
"devDependencies": {
"@edx/frontend-build": "^2.0.5",
"codecov": "^3.6.1",
"es-check": "^5.1.0",
"glob": "^7.1.6",
"husky": "^3.1.0",
"jest": "^24.9.0",
"reactifex": "^1.1.1"
"@edx/browserslist-config": "1.1.1",
"@edx/frontend-build": "^12.8.27",
"@edx/reactifex": "2.1.1",
"@pact-foundation/pact": "^11.0.2",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"@testing-library/user-event": "13.5.0",
"axios-mock-adapter": "1.20.0",
"copy-webpack-plugin": "^11.0.0",
"es-check": "6.2.1",
"husky": "7.0.4",
"jest": "29.5.0",
"rosie": "2.1.0"
}
}

View File

@@ -1,10 +1,14 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Course | edX</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<title>Course | <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<% if (htmlWebpackPlugin.options.OPTIMIZELY_PROJECT_ID) { %>
<script src="https://www.edx.org/optimizelyjs/<%= htmlWebpackPlugin.options.OPTIMIZELY_PROJECT_ID %>.js"></script>
<% } %>
</head>
<body>
<div id="root"></div>

View File

@@ -0,0 +1,11 @@
body a {
color: #00688D;
}
body.inline-link a {
text-decoration: underline;
}
body.small {
font-size: 0.875rem;
}

View File

@@ -5,5 +5,12 @@
"patch": {
"automerge": true
},
"rebaseStalePrs": true
"rebaseStalePrs": true,
"packageRules": [
{
"matchPackagePatterns": ["@edx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
]
}

View File

@@ -0,0 +1,137 @@
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
FormattedMessage, FormattedDate, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
import { Alert, Hyperlink } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import messages from './messages';
const AccessExpirationAlert = ({ intl, payload }) => {
const {
accessExpiration,
courseId,
org,
userTimezone,
analyticsPageName,
} = payload;
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
if (!accessExpiration) {
return null;
}
const {
expirationDate,
upgradeDeadline,
upgradeUrl,
} = accessExpiration;
const logClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: 'FBE_banner',
linkName: `${analyticsPageName}_audit_access_expires`,
linkType: 'link',
pageName: analyticsPageName,
});
};
let deadlineMessage = null;
if (upgradeDeadline && upgradeUrl) {
deadlineMessage = (
<>
<br />
<FormattedMessage
id="learning.accessExpiration.deadline"
defaultMessage="Upgrade by {date} to get unlimited access to the course as long as it exists on the site."
description="Warning shown to learner to upgrade while they are enrolled on the audit version and it's possible to upgrade"
values={{
date: (
<FormattedDate
key="accessExpirationUpgradeDeadline"
day="numeric"
month="short"
year="numeric"
value={upgradeDeadline}
{...timezoneFormatArgs}
/>
),
}}
/>
&nbsp;
<Hyperlink
className="font-weight-bold"
style={{ textDecoration: 'underline' }}
destination={upgradeUrl}
onClick={logClick}
>
{intl.formatMessage(messages.upgradeNow)}
</Hyperlink>
</>
);
}
return (
<Alert variant="info" icon={Info}>
<span className="font-weight-bold">
<FormattedMessage
id="learning.accessExpiration.header"
defaultMessage="Audit Access Expires {date}"
description="Headline for auditing deadline"
values={{
date: (
<FormattedDate
key="accessExpirationHeaderDate"
day="numeric"
month="short"
year="numeric"
value={expirationDate}
{...timezoneFormatArgs}
/>
),
}}
/>
</span>
<br />
<FormattedMessage
id="learning.accessExpiration.body"
defaultMessage="You lose all access to this course, including your progress, on {date}."
description="Message body to tell learner the consequences of course expiration."
values={{
date: (
<FormattedDate
key="accessExpirationBodyDate"
day="numeric"
month="short"
year="numeric"
value={expirationDate}
{...timezoneFormatArgs}
/>
),
}}
/>
{deadlineMessage}
</Alert>
);
};
AccessExpirationAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
accessExpiration: PropTypes.shape({
expirationDate: PropTypes.string.isRequired,
masqueradingExpiredCourse: PropTypes.bool.isRequired,
upgradeDeadline: PropTypes.string,
upgradeUrl: PropTypes.string,
}).isRequired,
courseId: PropTypes.string.isRequired,
org: PropTypes.string.isRequired,
userTimezone: PropTypes.string.isRequired,
analyticsPageName: PropTypes.string.isRequired,
}).isRequired,
};
export default injectIntl(AccessExpirationAlert);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
import { PageBanner } from '@edx/paragon';
const AccessExpirationMasqueradeBanner = ({ payload }) => {
const {
expirationDate,
userTimezone,
} = payload;
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
return (
<PageBanner variant="warning">
<FormattedMessage
id="instructorToolbar.pageBanner.courseHasExpired"
defaultMessage="This learner no longer has access to this course. Their access expired on {date}."
description="It's a warning that is shown to course author when being masqueraded as learner, while the course has expired for the real learner."
values={{
date: <FormattedDate
key="instructorToolbar.pageBanner.accessExpirationDate"
value={expirationDate}
{...timezoneFormatArgs}
/>,
}}
/>
</PageBanner>
);
};
AccessExpirationMasqueradeBanner.propTypes = {
payload: PropTypes.shape({
expirationDate: PropTypes.string.isRequired,
userTimezone: PropTypes.string.isRequired,
}).isRequired,
};
export default AccessExpirationMasqueradeBanner;

View File

@@ -0,0 +1,51 @@
import React, { useMemo } from 'react';
import { useAlert } from '../../generic/user-messages';
import { useModel } from '../../generic/model-store';
const AccessExpirationAlert = React.lazy(() => import('./AccessExpirationAlert'));
const AccessExpirationMasqueradeBanner = React.lazy(() => import('./AccessExpirationMasqueradeBanner'));
function useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, topic, analyticsPageName) {
const isVisible = accessExpiration && !accessExpiration.masqueradingExpiredCourse; // If it exists, show it.
const payload = useMemo(() => ({
accessExpiration,
courseId,
org,
userTimezone,
analyticsPageName,
}), [accessExpiration, analyticsPageName, courseId, org, userTimezone]);
useAlert(isVisible, {
code: 'clientAccessExpirationAlert',
payload,
topic,
});
return { clientAccessExpirationAlert: AccessExpirationAlert };
}
export function useAccessExpirationMasqueradeBanner(courseId, tab) {
const {
userTimezone,
} = useModel('courseHomeMeta', courseId);
const {
accessExpiration,
} = useModel(tab, courseId);
const isVisible = accessExpiration && accessExpiration.masqueradingExpiredCourse;
const expirationDate = accessExpiration && accessExpiration.expirationDate;
const payload = useMemo(() => ({
expirationDate,
userTimezone,
}), [expirationDate, userTimezone]);
useAlert(isVisible, {
code: 'clientAccessExpirationMasqueradeBanner',
payload,
topic: 'instructor-toolbar-alerts',
});
return { clientAccessExpirationMasqueradeBanner: AccessExpirationMasqueradeBanner };
}
export default useAccessExpirationAlert;

View File

@@ -0,0 +1 @@
export { default, useAccessExpirationMasqueradeBanner } from './hooks';

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
upgradeNow: {
id: 'learning.accessExpiration.upgradeNow',
defaultMessage: 'Upgrade now',
description: 'The anchor text for the upgrading link',
},
});
export default messages;

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert, Hyperlink } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
import genericMessages from './messages';
const ActiveEnterpriseAlert = ({ intl, payload }) => {
const { text, courseId } = payload;
const changeActiveEnterprise = (
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={
`${getConfig().LMS_BASE_URL}/enterprise/select/active/?success_url=${encodeURIComponent(
`${global.location.origin}/course/${courseId}/home`,
)}`
}
>
{intl.formatMessage(genericMessages.changeActiveEnterpriseLowercase)}
</Hyperlink>
);
return (
<Alert variant="warning" icon={WarningFilled}>
{text}
<FormattedMessage
id="learning.activeEnterprise.alert"
description="Prompts the user to log-in with the correct enterprise to access the course content."
defaultMessage=" {changeActiveEnterprise}."
values={{
changeActiveEnterprise,
}}
/>
</Alert>
);
};
ActiveEnterpriseAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
text: PropTypes.string,
courseId: PropTypes.string,
}).isRequired,
};
export default injectIntl(ActiveEnterpriseAlert);

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import {
initializeTestStore, render, screen,
} from '../../setupTest';
import ActiveEnterpriseAlert from './ActiveEnterpriseAlert';
describe('ActiveEnterpriseAlert', () => {
const mockData = {
payload: {
text: 'test message',
courseId: 'test-course-id',
},
};
beforeAll(async () => {
await initializeTestStore({ excludeFetchCourse: true, excludeFetchSequence: true });
});
it('Shows alert message and links', () => {
render(<ActiveEnterpriseAlert {...mockData} />);
expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByText('test message', { exact: false })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'change enterprise now' })).toHaveAttribute('href', `${getConfig().LMS_BASE_URL}/enterprise/select/active/?success_url=http%3A%2F%2Flocalhost%2Fcourse%2Ftest-course-id%2Fhome`);
});
});

View File

@@ -0,0 +1,28 @@
import React, { useMemo } from 'react';
import { ALERT_TYPES, useAlert } from '../../generic/user-messages';
import { useModel } from '../../generic/model-store';
const ActiveEnterpriseAlert = React.lazy(() => import('./ActiveEnterpriseAlert'));
export default function useActiveEnterpriseAlert(courseId) {
const { courseAccess } = useModel('courseHomeMeta', courseId);
/**
* This alert should render if
* 1. course access code is incorrect_active_enterprise
*/
const isVisible = courseAccess && !courseAccess.hasAccess && courseAccess.errorCode === 'incorrect_active_enterprise';
const payload = useMemo(() => ({
text: courseAccess && courseAccess.userMessage,
courseId,
}), [courseAccess, courseId]);
useAlert(isVisible, {
code: 'clientActiveEnterpriseAlert',
topic: 'outline',
dismissible: false,
type: ALERT_TYPES.ERROR,
payload,
});
return { clientActiveEnterpriseAlert: ActiveEnterpriseAlert };
}

View File

@@ -0,0 +1,3 @@
import useActiveEnterpriseAlert from './hooks';
export default useActiveEnterpriseAlert;

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
changeActiveEnterpriseLowercase: {
id: 'learning.activeEnterprise.change.alert',
defaultMessage: 'change enterprise now',
description: 'Text in a link, prompting the user to change active enterprise. Used in learning.activeEnterprise.change.alert"',
},
});
export default messages;

View File

@@ -0,0 +1,105 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
FormattedDate,
FormattedMessage,
FormattedRelativeTime,
FormattedTime,
} from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { useModel } from '../../generic/model-store';
const DAY_SEC = 24 * 60 * 60; // in seconds
const DAY_MS = DAY_SEC * 1000; // in ms
const YEAR_SEC = 365 * DAY_SEC; // in seconds
const CourseStartAlert = ({ payload }) => {
const {
courseId,
} = payload;
const {
start: startDate,
userTimezone,
} = useModel('courseHomeMeta', courseId);
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
const delta = new Date(startDate) - new Date();
const timeRemaining = (
<FormattedRelativeTime
key="timeRemaining"
value={delta / 1000}
numeric="auto"
// 1 year interval to help auto format. It won't format without updateIntervalInSeconds.
updateIntervalInSeconds={YEAR_SEC}
{...timezoneFormatArgs}
/>
);
if (delta < DAY_MS) {
return (
<Alert variant="info" icon={Info}>
<FormattedMessage
id="learning.outline.alert.start.short"
defaultMessage="Course starts {timeRemaining} at {courseStartTime}."
description="Used when the time remaining is less than a day away."
values={{
courseStartTime: (
<FormattedTime
key="courseStartTime"
day="numeric"
month="short"
year="numeric"
timeZoneName="short"
value={startDate}
{...timezoneFormatArgs}
/>
),
timeRemaining,
}}
/>
</Alert>
);
}
return (
<Alert variant="info" icon={Info}>
<strong>
<FormattedMessage
id="learning.outline.alert.end.long"
defaultMessage="Course starts {timeRemaining} on {courseStartDate}."
description="Used when the time remaining is more than a day away."
values={{
courseStartDate: (
<FormattedDate
key="courseStartDate"
day="numeric"
month="short"
year="numeric"
value={startDate}
{...timezoneFormatArgs}
/>
),
timeRemaining,
}}
/>
</strong>
<br />
<FormattedMessage
id="learning.outline.alert.end.calendar"
defaultMessage="Dont forget to add a calendar reminder!"
description="It's just a recommendation for learners to set a reminder for the course starting date and is shown when the course starting date is more than a day. "
/>
</Alert>
);
};
CourseStartAlert.propTypes = {
payload: PropTypes.shape({
courseId: PropTypes.string,
}).isRequired,
};
export default CourseStartAlert;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
import { PageBanner } from '@edx/paragon';
import { useModel } from '../../generic/model-store';
const CourseStartMasqueradeBanner = ({ payload }) => {
const {
courseId,
} = payload;
const {
start,
userTimezone,
} = useModel('courseHomeMeta', courseId);
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
return (
<PageBanner variant="warning">
<FormattedMessage
id="instructorToolbar.pageBanner.courseHasNotStarted"
defaultMessage="This learner does not yet have access to this course. The course starts on {date}."
description="It's a warning that is shown to course author when being masqueraded as learner, while the course hasn't started for the real learner yet."
values={{
date: <FormattedDate
key="instructorToolbar.pageBanner.courseStartDate"
value={start}
{...timezoneFormatArgs}
/>,
}}
/>
</PageBanner>
);
};
CourseStartMasqueradeBanner.propTypes = {
payload: PropTypes.shape({
courseId: PropTypes.string.isRequired,
}).isRequired,
};
export default CourseStartMasqueradeBanner;

View File

@@ -0,0 +1,62 @@
import React, { useMemo } from 'react';
import { useAlert } from '../../generic/user-messages';
import { useModel } from '../../generic/model-store';
const CourseStartAlert = React.lazy(() => import('./CourseStartAlert'));
const CourseStartMasqueradeBanner = React.lazy(() => import('./CourseStartMasqueradeBanner'));
function IsStartDateInFuture(courseId) {
const {
start,
} = useModel('courseHomeMeta', courseId);
const today = new Date();
const startDate = new Date(start);
return startDate > today;
}
function useCourseStartAlert(courseId) {
const {
isEnrolled,
} = useModel('courseHomeMeta', courseId);
const isVisible = isEnrolled && IsStartDateInFuture(courseId);
const payload = useMemo(() => ({
courseId,
}), [courseId]);
useAlert(isVisible, {
code: 'clientCourseStartAlert',
payload,
topic: 'outline-course-alerts',
});
return {
clientCourseStartAlert: CourseStartAlert,
};
}
export function useCourseStartMasqueradeBanner(courseId, tab) {
const {
isMasquerading,
} = useModel('courseHomeMeta', courseId);
const isVisible = isMasquerading && tab === 'progress' && IsStartDateInFuture(courseId);
const payload = useMemo(() => ({
courseId,
}), [courseId]);
useAlert(isVisible, {
code: 'clientCourseStartMasqueradeBanner',
payload,
topic: 'instructor-toolbar-alerts',
});
return {
clientCourseStartMasqueradeBanner: CourseStartMasqueradeBanner,
};
}
export default useCourseStartAlert;

View File

@@ -0,0 +1 @@
export { default, useCourseStartMasqueradeBanner } from './hooks';

View File

@@ -0,0 +1,70 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import PropTypes from 'prop-types';
import { Alert, Button } from '@edx/paragon';
import { Info, WarningFilled } from '@edx/paragon/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { useModel } from '../../generic/model-store';
import messages from './messages';
import useEnrollClickHandler from './clickHook';
const EnrollmentAlert = ({ intl, payload }) => {
const {
canEnroll,
courseId,
extraText,
isStaff,
} = payload;
const {
org,
} = useModel('courseHomeMeta', courseId);
const { enrollClickHandler, loading } = useEnrollClickHandler(
courseId,
org,
intl.formatMessage(messages.success),
);
let text = intl.formatMessage(messages.alert);
let type = 'warning';
let icon = WarningFilled;
if (isStaff) {
text = intl.formatMessage(messages.staffAlert);
type = 'info';
icon = Info;
} else if (extraText) {
text = `${text} ${extraText}`;
}
const button = canEnroll && (
<Button disabled={loading} variant="link" className="p-0 border-0 align-top mx-1" size="sm" style={{ textDecoration: 'underline' }} onClick={enrollClickHandler}>
{intl.formatMessage(messages.enrollNowSentence)}
</Button>
);
return (
<Alert variant={type} icon={icon}>
<div className="d-flex">
{text}
{button}
{loading && <FontAwesomeIcon icon={faSpinner} spin />}
</div>
</Alert>
);
};
EnrollmentAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
canEnroll: PropTypes.bool,
courseId: PropTypes.string,
extraText: PropTypes.string,
isStaff: PropTypes.bool,
}).isRequired,
};
export default injectIntl(EnrollmentAlert);

View File

@@ -0,0 +1,35 @@
import { useContext, useState, useCallback } from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { UserMessagesContext, ALERT_TYPES } from '../../generic/user-messages';
import { postCourseEnrollment } from './data/api';
// Separated into its own file to avoid a circular dependency inside this directory
function useEnrollClickHandler(courseId, orgId, successText) {
const [loading, setLoading] = useState(false);
const { addFlash } = useContext(UserMessagesContext);
const enrollClickHandler = useCallback(() => {
setLoading(true);
postCourseEnrollment(courseId).then(() => {
addFlash({
dismissible: true,
flash: true,
text: successText,
type: ALERT_TYPES.SUCCESS,
topic: 'course',
});
setLoading(false);
sendTrackEvent('edx.bi.user.course-home.enrollment', {
org_key: orgId,
courserun_key: courseId,
});
global.location.reload();
});
}, [addFlash, courseId, orgId, successText]);
return { enrollClickHandler, loading };
}
export default useEnrollClickHandler;

View File

@@ -0,0 +1,9 @@
/* eslint-disable import/prefer-default-export */
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';
export async function postCourseEnrollment(courseId) {
const url = `${getConfig().LMS_BASE_URL}/api/enrollment/v1/enrollment`;
const { data } = await getAuthenticatedHttpClient().post(url, { course_details: { course_id: courseId } });
return data;
}

View File

@@ -0,0 +1,39 @@
/* eslint-disable import/prefer-default-export */
import React, {
useContext, useMemo,
} from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import { useAlert } from '../../generic/user-messages';
import { useModel } from '../../generic/model-store';
const EnrollmentAlert = React.lazy(() => import('./EnrollmentAlert'));
export function useEnrollmentAlert(courseId) {
const { authenticatedUser } = useContext(AppContext);
const course = useModel('courseHomeMeta', courseId);
const outline = useModel('outline', courseId);
const enrolledUser = course && course.isEnrolled !== undefined && course.isEnrolled;
const privateOutline = outline && outline.courseBlocks && !outline.courseBlocks.courses;
/**
* This alert should render if
* 1. the user is not enrolled,
* 2. the user is authenticated, AND
* 3. the course is private.
*/
const isVisible = !enrolledUser && authenticatedUser !== null && privateOutline;
const payload = useMemo(() => ({
canEnroll: outline && outline.enrollAlert ? outline.enrollAlert.canEnroll : false,
courseId,
extraText: outline && outline.enrollAlert ? outline.enrollAlert.extraText : '',
isStaff: course && course.isStaff,
}), [course, courseId, outline]);
useAlert(isVisible, {
code: 'clientEnrollmentAlert',
payload,
topic: 'outline',
});
return { clientEnrollmentAlert: EnrollmentAlert };
}

View File

@@ -0,0 +1 @@
export { useEnrollmentAlert as default } from './hooks';

View File

@@ -0,0 +1,32 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
alert: {
id: 'learning.enrollment.alert',
defaultMessage: 'You must be enrolled in the course to see course content.',
description: 'Message shown to indicate that a user needs to enroll in a course prior to viewing the course content. Shown as part of an alert, along with a link to enroll.',
},
staffAlert: {
id: 'learning.staff.enrollment.alert',
defaultMessage: 'You are viewing this course as staff, and are not enrolled.',
description: 'Message shown to indicate that a user is not enrolled, but is able to view a course anyway because they are staff. Shown as part of an alert, along with a link to enroll.',
},
enrollNowInline: {
id: 'learning.enrollment.enrollNow.Inline',
defaultMessage: 'Enroll now',
description: 'A link prompting the user to click on it to enroll in the currently viewed course.'
+ 'This text is meant to be used at the beginning of a sentence (example: Enroll now to view course content.)',
},
enrollNowSentence: {
id: 'learning.enrollment.enrollNow.Sentence',
defaultMessage: 'Enroll now.',
description: 'A link prompting the user to click on it to enroll in the currently viewed course.',
},
success: {
id: 'learning.enrollment.success',
defaultMessage: "You've successfully enrolled in this course!",
description: 'A message telling the user that their course enrollment was successful.',
},
});
export default messages;

View File

@@ -0,0 +1,132 @@
import React, { useState } from 'react';
import Cookies from 'js-cookie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import {
AlertModal,
Button,
Spinner,
Icon,
} from '@edx/paragon';
import { Check, ArrowForward } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendActivationEmail } from '../../courseware/data';
import messages from './messages';
const AccountActivationAlert = ({
intl,
}) => {
const [showModal, setShowModal] = useState(false);
const [showSpinner, setShowSpinner] = useState(false);
const [showCheck, setShowCheck] = useState(false);
const handleOnClick = () => {
setShowSpinner(true);
setShowCheck(false);
sendActivationEmail().then(() => {
setShowSpinner(false);
setShowCheck(true);
});
};
const showAccountActivationAlert = Cookies.get('show-account-activation-popup');
if (showAccountActivationAlert !== undefined) {
Cookies.remove('show-account-activation-popup', { path: '/', domain: process.env.SESSION_COOKIE_DOMAIN });
// extra check to make sure cookie was removed before updating the state. Updating the state without removal
// of cookie would make it infinite rendering
if (Cookies.get('show-account-activation-popup') === undefined) {
setShowModal(true);
}
}
const button = (
<Button
variant="primary"
className=""
onClick={() => setShowModal(false)}
>
<FormattedMessage
id="account-activation.alert.button"
defaultMessage="Continue to {siteName}"
description="account activation alert continue button"
values={{
siteName: getConfig().SITE_NAME,
}}
/>
<Icon src={ArrowForward} className="ml-1 d-inline-block align-bottom" />
</Button>
);
const children = () => {
let bodyContent;
const message = (
<FormattedMessage
id="account-activation.alert.message"
defaultMessage="We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or
{sendEmailTag}."
description="Message for account activation alert which is shown after the registration"
values={{
boldEmail: <b>{getAuthenticatedUser() && getAuthenticatedUser().email}</b>,
sendEmailTag: (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a href="#" role="button" onClick={handleOnClick}>
<FormattedMessage
id="account-activation.resend.link"
defaultMessage="resend the email"
description="Message for resend link in account activation alert which is shown after the registration"
/>
</a>
),
}}
/>
);
bodyContent = (
<div>
{message}
</div>
);
if (!showCheck && showSpinner) {
bodyContent = (
<div>
{message}
<Spinner
animation="border"
variant="secondary"
style={{ height: '1.5rem', width: '1.5rem' }}
/>
</div>
);
}
if (showCheck && !showSpinner) {
bodyContent = (
<div>
{message}
<Icon
src={Check}
style={{ height: '1.7rem', width: '1.25rem' }}
className="text-success-500 d-inline-block position-fixed"
/>
</div>
);
}
return bodyContent;
};
return (
<AlertModal
isOpen={showModal}
title={intl.formatMessage(messages.accountActivationAlertTitle)}
footerNode={button}
onClose={() => ({})}
>
{children()}
</AlertModal>
);
};
AccountActivationAlert.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(AccountActivationAlert);

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { getLoginRedirectUrl } from '@edx/frontend-platform/auth';
import { Alert, Hyperlink } from '@edx/paragon';
import { WarningFilled } from '@edx/paragon/icons';
import genericMessages from '../../generic/messages';
const LogistrationAlert = ({ intl }) => {
const signIn = (
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={`${getLoginRedirectUrl(global.location.href)}`}
>
{intl.formatMessage(genericMessages.signInLowercase)}
</Hyperlink>
);
// TODO: Pull this registration URL building out into a function, like the login one above.
// This is complicated by the fact that we don't have a REGISTER_URL env variable available.
const register = (
<Hyperlink
style={{ textDecoration: 'underline' }}
destination={`${getConfig().LMS_BASE_URL}/register?next=${encodeURIComponent(global.location.href)}`}
>
{intl.formatMessage(genericMessages.registerLowercase)}
</Hyperlink>
);
return (
<Alert variant="warning" icon={WarningFilled}>
<FormattedMessage
id="learning.logistration.alert"
description="Prompts the user to sign in or register to see course content."
defaultMessage="To see course content, {signIn} or {register}."
values={{
signIn,
register,
}}
/>
</Alert>
);
};
LogistrationAlert.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(LogistrationAlert);

View File

@@ -0,0 +1,28 @@
/* eslint-disable import/prefer-default-export */
import React, { useContext } from 'react';
import { AppContext } from '@edx/frontend-platform/react';
import { ALERT_TYPES, useAlert } from '../../generic/user-messages';
import { useModel } from '../../generic/model-store';
const LogistrationAlert = React.lazy(() => import('./LogistrationAlert'));
export function useLogistrationAlert(courseId) {
const { authenticatedUser } = useContext(AppContext);
const outline = useModel('outline', courseId);
const privateOutline = outline && outline.courseBlocks && !outline.courseBlocks.courses;
/**
* This alert should render if
* 1. the user is not authenticated, AND
* 2. the course is private.
*/
const isVisible = authenticatedUser === null && privateOutline;
useAlert(isVisible, {
code: 'clientLogistrationAlert',
topic: 'outline',
dismissible: false,
type: ALERT_TYPES.ERROR,
});
return { clientLogistrationAlert: LogistrationAlert };
}

View File

@@ -0,0 +1 @@
export { useLogistrationAlert as default } from './hooks';

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
accountActivationAlertTitle: {
id: 'account-activation.alert.title',
defaultMessage: 'Activate your account so you can log back in',
description: 'Title for account activation alert which is shown after the registration',
},
});
export default messages;

View File

@@ -0,0 +1,57 @@
import { useSelector } from 'react-redux';
import { useModel } from '../../generic/model-store';
import { ALERT_TYPES, useAlert } from '../../generic/user-messages';
import messages from './messages';
function useSequenceBannerTextAlert(sequenceId) {
const sequence = useModel('sequences', sequenceId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
// Show Alert that comes along with the sequence
useAlert(sequenceStatus === 'loaded' && sequence.bannerText, {
code: null,
dismissible: false,
text: sequence.bannerText,
type: ALERT_TYPES.INFO,
topic: 'sequence',
});
}
function useSequenceEntranceExamAlert(courseId, sequenceId, intl) {
const course = useModel('coursewareMeta', courseId);
const sequence = useModel('sequences', sequenceId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const {
entranceExamCurrentScore,
entranceExamEnabled,
entranceExamId,
entranceExamMinimumScorePct,
entranceExamPassed,
} = course.entranceExamData || {};
const entranceExamAlertVisible = sequenceStatus === 'loaded' && entranceExamEnabled && entranceExamId === sequence.sectionId;
let entranceExamText;
if (entranceExamPassed) {
entranceExamText = intl.formatMessage(
messages.entranceExamTextPassed,
{ entranceExamCurrentScore: entranceExamCurrentScore * 100 },
);
} else {
entranceExamText = intl.formatMessage(messages.entranceExamTextNotPassing, {
entranceExamCurrentScore: entranceExamCurrentScore * 100,
entranceExamMinimumScorePct: entranceExamMinimumScorePct * 100,
});
}
useAlert(entranceExamAlertVisible, {
code: null,
dismissible: false,
text: entranceExamText,
type: ALERT_TYPES.INFO,
topic: 'sequence',
});
}
export { useSequenceBannerTextAlert, useSequenceEntranceExamAlert };

View File

@@ -0,0 +1,14 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
entranceExamTextNotPassing: {
id: 'learn.sequence.entranceExamTextNotPassing',
defaultMessage: 'To access course materials, you must score {entranceExamMinimumScorePct}% or higher on this exam. Your current score is {entranceExamCurrentScore}%.',
},
entranceExamTextPassed: {
id: 'learn.sequence.entranceExamTextPassed',
defaultMessage: 'Your score is {entranceExamCurrentScore}%. You have passed the entrance exam.',
},
});
export default messages;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,74 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import NavTab from './NavTab';
function CourseTabsNavigation({ activeTabSlug, courseTabs, intl }) {
const courseNavTabs = courseTabs.map(({ slug, ...courseTab }) => (
<NavTab
isActive={slug === activeTabSlug}
key={slug}
{...courseTab}
/>
));
return (
<nav
aria-label={intl.formatMessage(messages['learn.navigation.course.tabs.label'])}
className="nav nav-underline-tabs"
>
{courseNavTabs}
</nav>
);
}
CourseTabsNavigation.propTypes = {
activeTabSlug: PropTypes.string,
courseTabs: PropTypes.arrayOf(PropTypes.shape({
title: PropTypes.string.isRequired,
priority: PropTypes.number.isRequired,
slug: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
})),
intl: intlShape.isRequired,
};
CourseTabsNavigation.defaultProps = {
activeTabSlug: undefined,
courseTabs: [
{
title: 'Course',
slug: 'course',
priority: 1,
url: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/',
},
{
title: 'Discussion',
slug: 'discussion',
priority: 2,
url: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/',
},
{
title: 'Wiki',
slug: 'wiki',
priority: 3,
url: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki',
},
{
title: 'Progress',
slug: 'progress',
priority: 4,
url: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress',
},
{
title: 'Instructor',
slug: 'instructor',
priority: 5,
url: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor',
},
],
};
export default injectIntl(CourseTabsNavigation);

View File

@@ -1,30 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default function NavTab(props) {
const {
isActive, url, title, ...attrs
} = props;
const className = classNames(
'nav-item nav-link',
{ active: isActive },
attrs.className,
);
return <a {...attrs} className={className} href={url}>{title}</a>;
}
NavTab.propTypes = {
className: PropTypes.string,
isActive: PropTypes.bool,
title: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
};
NavTab.defaultProps = {
className: undefined,
isActive: false,
};

View File

@@ -0,0 +1,122 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
import courseMetadataBase from '../../../shared/data/__factories__/courseMetadataBase.factory';
Factory.define('courseHomeMetadata')
.extend(courseMetadataBase)
.option('host', 'http://localhost:18000')
.attrs({
title: 'Demonstration Course',
is_self_paced: false,
is_enrolled: false,
is_staff: false,
can_view_certificate: true,
celebrations: null,
course_access: {
additional_context_user_message: null,
developer_message: null,
error_code: null,
has_access: true,
user_fragment: null,
user_message: null,
},
number: 'DemoX',
original_user_is_staff: false,
org: 'edX',
start: '2013-02-05T05:00:00Z',
user_timezone: 'UTC',
username: 'MockUser',
verified_mode: {
access_expiration_date: null,
currency: 'USD',
upgrade_url: 'http://localhost:18130/basket/add/?sku=8CF08E5',
sku: '8CF08E5',
price: 149,
currency_symbol: '$',
},
})
.attr('tabs', ['id', 'host'], (id, host) => [
Factory.build(
'tab',
{
title: 'Course',
priority: 0,
slug: 'courseware',
type: 'courseware',
},
{
courseId: id,
host,
path: 'course/',
},
),
Factory.build(
'tab',
{
title: 'Discussion',
priority: 1,
slug: 'discussion',
type: 'discussion',
},
{
courseId: id,
host,
path: 'discussion/forum/',
},
),
Factory.build(
'tab',
{
title: 'Wiki',
priority: 2,
slug: 'wiki',
type: 'wiki',
},
{
courseId: id,
host,
path: 'course_wiki',
},
),
Factory.build(
'tab',
{
title: 'Progress',
priority: 3,
slug: 'progress',
type: 'progress',
},
{
courseId: id,
host,
path: 'progress',
},
),
Factory.build(
'tab',
{
title: 'Instructor',
priority: 4,
slug: 'instructor',
type: 'instructor',
},
{
courseId: id,
host,
path: 'instructor',
},
),
Factory.build(
'tab',
{
title: 'Dates',
priority: 5,
slug: 'dates',
type: 'dates',
},
{
courseId: id,
host,
path: 'dates',
},
),
]);

View File

@@ -0,0 +1,222 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
// Sample data helpful when developing & testing, to see a variety of configurations.
// This set of data is not realistic (mix of having access and not), but it
// is intended to demonstrate many UI results.
Factory.define('datesTabData')
.attrs({
dates_banner_info: {
content_type_gating_enabled: false,
missed_gated_content: false,
missed_deadlines: false,
verified_upgrade_link: 'http://localhost:18130/basket/add/?sku=8CF08E5',
},
course_date_blocks: [
{
date: '2020-05-01T17:59:41Z',
date_type: 'course-start-date',
description: '',
learner_has_access: true,
link: '',
title: 'Course Starts',
extra_info: null,
},
{
assignment_type: 'Homework',
complete: true,
date: '2020-05-04T02:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'Multi Badges Completed',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2020-05-05T02:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'Multi Badges Past Due',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2020-05-27T02:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'Both Past Due 1',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2020-05-27T02:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'Both Past Due 2',
extra_info: null,
},
{
assignment_type: 'Homework',
complete: true,
date: '2020-05-28T08:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'One Completed/Due 1',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2020-05-28T08:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'One Completed/Due 2',
extra_info: null,
},
{
assignment_type: 'Homework',
complete: true,
date: '2020-05-29T08:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'Both Completed 1',
extra_info: null,
},
{
assignment_type: 'Homework',
complete: true,
date: '2020-05-29T08:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'Both Completed 2',
extra_info: null,
},
{
date: '2020-06-16T17:59:40.942669Z',
date_type: 'verified-upgrade-deadline',
description: "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.",
learner_has_access: true,
link: 'https://example.com/',
title: 'Upgrade to Verified Certificate',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-17T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: false,
link: 'https://example.com/',
title: 'One Verified 1',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-17T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'One Verified 2',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-17T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'ORA Verified 2',
extra_info: "ORA Dates are set by the instructor, and can't be changed",
},
{
assignment_type: 'Homework',
date: '2030-08-18T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: false,
link: 'https://example.com/',
title: 'Both Verified 1',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-18T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: false,
link: 'https://example.com/',
title: 'Both Verified 2',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-19T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'One Unreleased 1',
},
{
assignment_type: 'Homework',
date: '2030-08-19T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
link: 'https://example.com/',
title: 'One Unreleased 2',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-20T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'Both Unreleased 1',
extra_info: null,
},
{
assignment_type: 'Homework',
date: '2030-08-20T05:59:40.942669Z',
date_type: 'assignment-due-date',
description: '',
learner_has_access: true,
title: 'Both Unreleased 2',
extra_info: null,
},
{
date: '2030-08-23T00:00:00Z',
date_type: 'course-end-date',
description: '',
learner_has_access: true,
link: '',
title: 'Course Ends',
extra_info: null,
},
{
date: '2030-09-01T00:00:00Z',
date_type: 'verification-deadline-date',
description: 'You must successfully complete verification before this date to qualify for a Verified Certificate.',
learner_has_access: false,
link: 'https://example.com/',
title: 'Verification Deadline',
extra_info: null,
},
],
has_ended: false,
learner_is_full_access: true,
});

View File

@@ -0,0 +1,5 @@
import './courseHomeMetadata.factory';
import './datesTabData.factory';
import './outlineTabData.factory';
import './progressTabData.factory';
import './upgradeNotificationData.factory';

View File

@@ -0,0 +1,66 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
import { buildMinimalCourseBlocks } from '../../../shared/data/__factories__/courseBlocks.factory';
Factory.define('outlineTabData')
.option('courseId', 'course-v1:edX+DemoX+Demo_Course')
.option('host', 'http://localhost:18000')
.option('date_blocks', [])
.attr('course_blocks', ['courseId'], courseId => {
const { courseBlocks } = buildMinimalCourseBlocks(courseId);
return {
blocks: courseBlocks.blocks,
};
})
.attr('dates_widget', ['date_blocks'], (dateBlocks) => ({
course_date_blocks: dateBlocks,
}))
.attr('resume_course', ['host', 'courseId'], (host, courseId) => ({
has_visited_course: false,
url: `${host}/courses/${courseId}/jump_to/block-v1:edX+Test+Block@12345abcde`,
}))
.attr('verified_mode', ['host'], (host) => ({
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
currency_symbol: '$',
price: 149,
sku: 'ABCD1234',
upgrade_url: `${host}/dashboard`,
}))
.attrs({
course_access_redirect: false,
has_scheduled_content: null,
access_expiration: null,
can_show_upgrade_sock: false,
cert_data: {
cert_status: null,
cert_web_view_url: null,
certificate_available_date: null,
},
course_goals: {
goal_options: [],
selected_goal: null,
weekly_learning_goal_enabled: false,
days_per_week: null,
subscribed_to_reminders: null,
},
course_tools: [
{
analytics_id: 'edx.bookmarks',
title: 'Bookmarks',
url: 'https://example.com/bookmarks',
},
],
dates_banner_info: {
content_type_gating_enabled: false,
missed_gated_content: false,
missed_deadlines: false,
},
enroll_alert: {
can_enroll: true,
extra_text: 'Contact the administrator.',
},
handouts_html: '<ul><li>Handout 1</li></ul>',
offer: null,
welcome_message_html: '<p>Welcome to this course!</p>',
});

View File

@@ -0,0 +1,83 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
// Sample data helpful when developing & testing, to see a variety of configurations.
// This set of data may not be realistic, but it is intended to demonstrate many UI results.
Factory.define('progressTabData')
.attrs({
access_expiration: null,
end: '3027-03-31T00:00:00Z',
certificate_data: {},
completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 0,
},
course_grade: {
letter_grade: 'pass',
percent: 1,
is_passing: true,
},
credit_course_requirements: null,
section_scores: [
{
display_name: 'First section',
subsections: [
{
assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection',
learner_has_access: true,
has_graded_assignment: true,
num_points_earned: 0,
num_points_possible: 3,
percent_graded: 0.0,
problem_scores: [{ earned: 0, possible: 1 }, { earned: 0, possible: 1 }, { earned: 0, possible: 1 }],
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection',
},
],
},
{
display_name: 'Second section',
subsections: [
{
assignment_type: 'Homework',
display_name: 'Second subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 1,
percent_graded: 1.0,
problem_scores: [{ earned: 1, possible: 1 }],
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection',
},
],
},
],
enrollment_mode: 'audit',
grading_policy: {
assignment_policies: [
{
num_droppable: 1,
num_total: 2,
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
pass: 0.75,
},
},
has_scheduled_content: false,
studio_url: 'http://studio.edx.org/settings/grading/course-v1:edX+Test+run',
user_has_passing_grade: false,
verification_data: {
link: null,
status: 'none',
status_date: null,
},
verified_mode: null,
});

View File

@@ -0,0 +1,25 @@
import { Factory } from 'rosie'; // eslint-disable-line import/no-extraneous-dependencies
Factory.define('upgradeNotificationData')
.option('host', 'http://localhost:18000')
.option('dateBlocks', [])
.option('offer', null)
.option('userTimezone', null)
.option('contentTypeGatingEnabled', false)
.attr('courseId', 'course-v1:edX+DemoX+Demo_Course')
.attr('upsellPageName', 'test')
.attr('verifiedMode', ['host'], (host) => ({
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
currencySymbol: '$',
price: 149,
sku: 'ABCD1234',
upgradeUrl: `${host}/dashboard`,
}))
.attr('org', 'edX')
.attrs({
accessExpiration: {
expiration_date: '1950-07-13T02:04:49.040006Z',
},
})
.attr('timeOffsetMillis', 0);

View File

@@ -0,0 +1,717 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Data layer integration tests Test fetchDatesTab Should fetch, normalize, and save metadata 1`] = `
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"dates": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"courseDateBlocks": Array [
Object {
"date": "2020-05-01T17:59:41Z",
"dateType": "course-start-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "",
"title": "Course Starts",
},
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-04T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Multi Badges Completed",
},
Object {
"assignmentType": "Homework",
"date": "2020-05-05T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Multi Badges Past Due",
},
Object {
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Past Due 1",
},
Object {
"assignmentType": "Homework",
"date": "2020-05-27T02:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Past Due 2",
},
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-28T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Completed/Due 1",
},
Object {
"assignmentType": "Homework",
"date": "2020-05-28T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Completed/Due 2",
},
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Completed 1",
},
Object {
"assignmentType": "Homework",
"complete": true,
"date": "2020-05-29T08:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Both Completed 2",
},
Object {
"date": "2020-06-16T17:59:40.942669Z",
"dateType": "verified-upgrade-deadline",
"description": "Don't miss the opportunity to highlight your new knowledge and skills by earning a verified certificate.",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "Upgrade to Verified Certificate",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "One Verified 1",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Verified 2",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-17T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": "ORA Dates are set by the instructor, and can't be changed",
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "ORA Verified 2",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Both Verified 1",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-18T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Both Verified 2",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"learnerHasAccess": true,
"title": "One Unreleased 1",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-19T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "https://example.com/",
"title": "One Unreleased 2",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Both Unreleased 1",
},
Object {
"assignmentType": "Homework",
"date": "2030-08-20T05:59:40.942669Z",
"dateType": "assignment-due-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"title": "Both Unreleased 2",
},
Object {
"date": "2030-08-23T00:00:00Z",
"dateType": "course-end-date",
"description": "",
"extraInfo": null,
"learnerHasAccess": true,
"link": "",
"title": "Course Ends",
},
Object {
"date": "2030-09-01T00:00:00Z",
"dateType": "verification-deadline-date",
"description": "You must successfully complete verification before this date to qualify for a Verified Certificate.",
"extraInfo": null,
"learnerHasAccess": false,
"link": "https://example.com/",
"title": "Verification Deadline",
},
],
"datesBannerInfo": Object {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
"verifiedUpgradeLink": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
"hasEnded": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"learnerIsFullAccess": true,
},
},
},
"recommendations": Object {
"recommendationsStatus": "loading",
},
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;
exports[`Data layer integration tests Test fetchOutlineTab Should fetch, normalize, and save metadata 1`] = `
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"outline": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"accessExpiration": null,
"canShowUpgradeSock": false,
"certData": Object {
"certStatus": null,
"certWebViewUrl": null,
"certificateAvailableDate": null,
},
"courseBlocks": Object {
"courses": Object {
"block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object {
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionIds": Array [
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
],
"title": "bcdabcdabcdabcdabcdabcdabcdabcd3",
},
},
"sections": Object {
"block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2": Object {
"complete": false,
"courseId": "course-v1:edX+DemoX+Demo_Course",
"id": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"resumeBlock": false,
"sequenceIds": Array [
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
],
"title": "Title of Section",
},
},
"sequences": Object {
"block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1": Object {
"complete": false,
"description": null,
"due": null,
"effortActivities": 2,
"effortTime": 15,
"icon": null,
"id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@bcdabcdabcdabcdabcdabcdabcdabcd1",
"sectionId": "block-v1:edX+DemoX+Demo_Course+type@chapter+block@bcdabcdabcdabcdabcdabcdabcdabcd2",
"showLink": true,
"title": "Title of Sequence",
},
},
},
"courseGoals": Object {
"daysPerWeek": null,
"goalOptions": Array [],
"selectedGoal": null,
"subscribedToReminders": null,
"weeklyLearningGoalEnabled": false,
},
"courseTools": Array [
Object {
"analyticsId": "edx.bookmarks",
"title": "Bookmarks",
"url": "https://example.com/bookmarks",
},
],
"datesBannerInfo": Object {
"contentTypeGatingEnabled": false,
"missedDeadlines": false,
"missedGatedContent": false,
},
"datesWidget": Object {
"courseDateBlocks": Array [],
},
"enableProctoredExams": undefined,
"enrollAlert": Object {
"canEnroll": true,
"extraText": "Contact the administrator.",
},
"enrollmentMode": undefined,
"handoutsHtml": "<ul><li>Handout 1</li></ul>",
"hasEnded": undefined,
"hasScheduledContent": null,
"id": "course-v1:edX+DemoX+Demo_Course",
"offer": null,
"resumeCourse": Object {
"hasVisitedCourse": false,
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+Test+Block@12345abcde",
},
"timeOffsetMillis": 0,
"userHasPassingGrade": undefined,
"verifiedMode": Object {
"accessExpirationDate": "2050-01-01T12:00:00",
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "ABCD1234",
"upgradeUrl": "http://localhost:18000/dashboard",
},
"welcomeMessageHtml": "<p>Welcome to this course!</p>",
},
},
},
"recommendations": Object {
"recommendationsStatus": "loading",
},
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;
exports[`Data layer integration tests Test fetchProgressTab Should fetch, normalize, and save metadata 1`] = `
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course",
"courseStatus": "loaded",
"proctoringPanelStatus": "loading",
"targetUserId": undefined,
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
"sequenceId": null,
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"canViewCertificate": true,
"celebrations": null,
"courseAccess": Object {
"additionalContextUserMessage": null,
"developerMessage": null,
"errorCode": null,
"hasAccess": true,
"userFragment": null,
"userMessage": null,
},
"id": "course-v1:edX+DemoX+Demo_Course",
"isEnrolled": false,
"isMasquerading": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"start": "2013-02-05T05:00:00Z",
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course/",
},
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/discussion/forum/",
},
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/course_wiki",
},
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/progress",
},
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/instructor",
},
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/dates",
},
],
"title": "Demonstration Course",
"userTimezone": "UTC",
"username": "MockUser",
"verifiedMode": Object {
"accessExpirationDate": null,
"currency": "USD",
"currencySymbol": "$",
"price": 149,
"sku": "8CF08E5",
"upgradeUrl": "http://localhost:18130/basket/add/?sku=8CF08E5",
},
},
},
"progress": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
"accessExpiration": null,
"certificateData": Object {},
"completionSummary": Object {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"courseGrade": Object {
"isPassing": true,
"letterGrade": "pass",
"percent": 1,
},
"courseId": "course-v1:edX+DemoX+Demo_Course",
"creditCourseRequirements": null,
"end": "3027-03-31T00:00:00Z",
"enrollmentMode": "audit",
"gradesFeatureIsFullyLocked": false,
"gradesFeatureIsPartiallyLocked": false,
"gradingPolicy": Object {
"assignmentPolicies": Array [
Object {
"averageGrade": "1.00",
"numDroppable": 1,
"shortLabel": "HW",
"type": "Homework",
"weight": 1,
"weightedGrade": 1,
},
],
"gradeRange": Object {
"pass": 0.75,
},
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course",
"sectionScores": Array [
Object {
"displayName": "First section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"blockKey": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345",
"displayName": "First subsection",
"hasGradedAssignment": true,
"learnerHasAccess": true,
"numPointsEarned": 0,
"numPointsPossible": 3,
"percentGraded": 0,
"problemScores": Array [
Object {
"earned": 0,
"possible": 1,
},
Object {
"earned": 0,
"possible": 1,
},
Object {
"earned": 0,
"possible": 1,
},
],
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection",
},
],
},
Object {
"displayName": "Second section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"displayName": "Second subsection",
"hasGradedAssignment": true,
"numPointsEarned": 1,
"numPointsPossible": 1,
"percentGraded": 1,
"problemScores": Array [
Object {
"earned": 1,
"possible": 1,
},
],
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection",
},
],
},
],
"studioUrl": "http://studio.edx.org/settings/grading/course-v1:edX+Test+run",
"userHasPassingGrade": false,
"verificationData": Object {
"link": null,
"status": "none",
"statusDate": null,
},
"verifiedMode": null,
},
},
},
"recommendations": Object {
"recommendationsStatus": "loading",
},
"tours": Object {
"showCoursewareTour": false,
"showExistingUserCourseHomeTour": false,
"showNewUserCourseHomeModal": false,
"showNewUserCourseHomeTour": false,
"toursEnabled": false,
},
}
`;

447
src/course-home/data/api.js Normal file
View File

@@ -0,0 +1,447 @@
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logInfo } from '@edx/frontend-platform/logging';
import { appendBrowserTimezoneToUrl } from '../../utils';
const calculateAssignmentTypeGrades = (points, assignmentWeight, numDroppable) => {
let dropCount = numDroppable;
// Drop the lowest grades
while (dropCount && points.length >= dropCount) {
const lowestScore = Math.min(...points);
const lowestScoreIndex = points.indexOf(lowestScore);
points.splice(lowestScoreIndex, 1);
dropCount--;
}
let averageGrade = 0;
let weightedGrade = 0;
if (points.length) {
// Calculate the average grade for the assignment and round it. This rounding is not ideal and does not accurately
// reflect what a learner's grade would be, however, we must have parity with the current grading behavior that
// exists in edx-platform.
averageGrade = (points.reduce((a, b) => a + b, 0) / points.length).toFixed(2);
weightedGrade = averageGrade * assignmentWeight;
}
return { averageGrade, weightedGrade };
};
function normalizeAssignmentPolicies(assignmentPolicies, sectionScores) {
const gradeByAssignmentType = {};
assignmentPolicies.forEach(assignment => {
// Create an array with the number of total assignments and set the scores to 0
// as placeholders for assignments that have not yet been released
gradeByAssignmentType[assignment.type] = {
grades: Array(assignment.numTotal).fill(0),
numAssignmentsCreated: 0,
numTotalExpectedAssignments: assignment.numTotal,
};
});
sectionScores.forEach((chapter) => {
chapter.subsections.forEach((subsection) => {
if (!(subsection.hasGradedAssignment && subsection.showGrades && subsection.numPointsPossible)) {
return;
}
const {
assignmentType,
numPointsEarned,
numPointsPossible,
} = subsection;
// If a subsection's assignment type does not match an assignment policy in Studio,
// we won't be able to include it in this accumulation of grades by assignment type.
// This may happen if a course author has removed/renamed an assignment policy in Studio and
// neglected to update the subsection's of that assignment type
if (!gradeByAssignmentType[assignmentType]) {
return;
}
let {
numAssignmentsCreated,
} = gradeByAssignmentType[assignmentType];
numAssignmentsCreated++;
if (numAssignmentsCreated <= gradeByAssignmentType[assignmentType].numTotalExpectedAssignments) {
// Remove a placeholder grade so long as the number of recorded created assignments is less than the number
// of expected assignments
gradeByAssignmentType[assignmentType].grades.shift();
}
// Add the graded assignment to the list
gradeByAssignmentType[assignmentType].grades.push(numPointsEarned ? numPointsEarned / numPointsPossible : 0);
// Record the created assignment
gradeByAssignmentType[assignmentType].numAssignmentsCreated = numAssignmentsCreated;
});
});
return assignmentPolicies.map((assignment) => {
const { averageGrade, weightedGrade } = calculateAssignmentTypeGrades(
gradeByAssignmentType[assignment.type].grades,
assignment.weight,
assignment.numDroppable,
);
return {
averageGrade,
numDroppable: assignment.numDroppable,
shortLabel: assignment.shortLabel,
type: assignment.type,
weight: assignment.weight,
weightedGrade,
};
});
}
/**
* Tweak the metadata for consistency
* @param metadata the data to normalize
* @param rootSlug either 'courseware' or 'outline' depending on the context
* @returns {Object} The normalized metadata
*/
function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
const data = camelCaseObject(metadata);
return {
...data,
tabs: data.tabs.map(tab => ({
// The API uses "courseware" as a slug for both courseware and the outline tab.
// If needed, we switch it to "outline" here for
// use within the MFE to differentiate between course home and courseware.
slug: tab.tabId === 'courseware' ? rootSlug : tab.tabId,
title: tab.title,
url: tab.url,
})),
isMasquerading: data.originalUserIsStaff && !data.isStaff,
};
}
export function normalizeOutlineBlocks(courseId, blocks) {
const models = {
courses: {},
sections: {},
sequences: {},
};
Object.values(blocks).forEach(block => {
switch (block.type) {
case 'course':
models.courses[block.id] = {
id: courseId,
title: block.display_name,
sectionIds: block.children || [],
hasScheduledContent: block.has_scheduled_content,
};
break;
case 'chapter':
models.sections[block.id] = {
complete: block.complete,
id: block.id,
title: block.display_name,
resumeBlock: block.resume_block,
sequenceIds: block.children || [],
};
break;
case 'sequential':
models.sequences[block.id] = {
complete: block.complete,
description: block.description,
due: block.due,
effortActivities: block.effort_activities,
effortTime: block.effort_time,
icon: block.icon,
id: block.id,
// The presence of a URL for the sequence indicates that we want this sequence to be a clickable
// link in the outline (even though we ignore the given url and use an internal <Link> to ourselves).
showLink: !!block.lms_web_url,
title: block.display_name,
};
break;
default:
logInfo(`Unexpected course block type: ${block.type} with ID ${block.id}. Expected block types are course, chapter, and sequential.`);
}
});
// Next go through each list and use their child lists to decorate those children with a
// reference back to their parent.
Object.values(models.courses).forEach(course => {
if (Array.isArray(course.sectionIds)) {
course.sectionIds.forEach(sectionId => {
const section = models.sections[sectionId];
section.courseId = course.id;
});
}
});
Object.values(models.sections).forEach(section => {
if (Array.isArray(section.sequenceIds)) {
section.sequenceIds.forEach(sequenceId => {
if (sequenceId in models.sequences) {
models.sequences[sequenceId].sectionId = section.id;
} else {
logInfo(`Section ${section.id} has child block ${sequenceId}, but that block is not in the list of sequences.`);
}
});
}
});
return models;
}
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
let url = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`;
url = appendBrowserTimezoneToUrl(url);
const { data } = await getAuthenticatedHttpClient().get(url);
return normalizeCourseHomeCourseMetadata(data, rootSlug);
}
// For debugging purposes, you might like to see a fully loaded dates tab.
// Just uncomment the next few lines and the immediate 'return' in the function below
// import { Factory } from 'rosie';
// import './__factories__';
export async function getDatesTabData(courseId) {
// return camelCaseObject(Factory.build('datesTabData'));
const url = `${getConfig().LMS_BASE_URL}/api/course_home/dates/${courseId}`;
try {
const { data } = await getAuthenticatedHttpClient().get(url);
return camelCaseObject(data);
} catch (error) {
const httpErrorStatus = error?.response?.status;
if (httpErrorStatus === 401) {
// The backend sends this for unenrolled and unauthenticated learners, but we handle those cases by examining
// courseAccess in the metadata call, so just ignore this status for now.
return {};
}
if (httpErrorStatus === 403) {
// The backend sends this if there is a course access error and the user should be redirected. The redirect
// info is included in the course metadata request and will be handled there as long as this call returns
// without an error
return {};
}
throw error;
}
}
export async function getProgressTabData(courseId, targetUserId) {
let url = `${getConfig().LMS_BASE_URL}/api/course_home/progress/${courseId}`;
// If targetUserId is passed in, we will get the progress page data
// for the user with the provided id, rather than the requesting user.
if (targetUserId) {
url += `/${targetUserId}/`;
}
try {
const { data } = await getAuthenticatedHttpClient().get(url);
const camelCasedData = camelCaseObject(data);
camelCasedData.gradingPolicy.assignmentPolicies = normalizeAssignmentPolicies(
camelCasedData.gradingPolicy.assignmentPolicies,
camelCasedData.sectionScores,
);
// We replace gradingPolicy.gradeRange with the original data to preserve the intended casing for the grade.
// For example, if a grade range key is "A", we do not want it to be camel cased (i.e. "A" would become "a")
// in order to preserve a course team's desired grade formatting.
camelCasedData.gradingPolicy.gradeRange = data.grading_policy.grade_range;
camelCasedData.gradesFeatureIsFullyLocked = camelCasedData.completionSummary.lockedCount > 0;
camelCasedData.gradesFeatureIsPartiallyLocked = false;
if (camelCasedData.gradesFeatureIsFullyLocked) {
camelCasedData.sectionScores.forEach((chapter) => {
chapter.subsections.forEach((subsection) => {
// If something is eligible to be gated by content type gating and would show up on the progress page
if (subsection.assignmentType !== null && subsection.hasGradedAssignment && subsection.showGrades
&& (subsection.numPointsPossible > 0 || subsection.numPointsEarned > 0)) {
// but the learner still has access to it, then we are in a partially locked, rather than fully locked state
// since the learner has access to some (but not all) content that would normally be locked
if (subsection.learnerHasAccess) {
camelCasedData.gradesFeatureIsPartiallyLocked = true;
camelCasedData.gradesFeatureIsFullyLocked = false;
}
}
});
});
}
return camelCasedData;
} catch (error) {
const httpErrorStatus = error?.response?.status;
if (httpErrorStatus === 404) {
global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/progress`);
return {};
}
if (httpErrorStatus === 401) {
// The backend sends this for unenrolled and unauthenticated learners, but we handle those cases by examining
// courseAccess in the metadata call, so just ignore this status for now.
return {};
}
if (httpErrorStatus === 403) {
// The backend sends this if there is a course access error and the user should be redirected. The redirect
// info is included in the course metadata request and will be handled there as long as this call returns
// without an error
return {};
}
throw error;
}
}
export async function getProctoringInfoData(courseId, username) {
let url = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?is_learning_mfe=true&course_id=${encodeURIComponent(courseId)}`;
if (username) {
url += `&username=${encodeURIComponent(username)}`;
}
try {
const { data } = await getAuthenticatedHttpClient().get(url);
return data;
} catch (error) {
const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) {
return {};
}
throw error;
}
}
export async function getLiveTabIframe(courseId) {
const url = `${getConfig().LMS_BASE_URL}/api/course_live/iframe/${courseId}/`;
try {
const { data } = await getAuthenticatedHttpClient().get(url);
return data;
} catch (error) {
const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) {
return {};
}
throw error;
}
}
export function getTimeOffsetMillis(headerDate, requestTime, responseTime) {
// Time offset computation should move down into the HttpClient wrapper to maintain a global time correction reference
// Requires 'Access-Control-Expose-Headers: Date' on the server response per https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-expose-headers
let timeOffsetMillis = 0;
if (headerDate !== undefined) {
const headerTime = Date.parse(headerDate);
const roundTripMillis = requestTime - responseTime;
const localTime = responseTime - (roundTripMillis / 2); // Roughly compensate for transit time
timeOffsetMillis = headerTime - localTime;
}
return timeOffsetMillis;
}
export async function getOutlineTabData(courseId) {
const url = `${getConfig().LMS_BASE_URL}/api/course_home/outline/${courseId}`;
const requestTime = Date.now();
let tabData;
try {
tabData = await getAuthenticatedHttpClient().get(url);
} catch (error) {
const httpErrorStatus = error?.response?.status;
if (httpErrorStatus === 403) {
// The backend sends this if there is a course access error and the user should be redirected. The redirect
// info is included in the course metadata request and will be handled there as long as this call returns
// without an error
return {};
}
throw error;
}
const responseTime = Date.now();
const {
data,
headers,
} = tabData;
const accessExpiration = camelCaseObject(data.access_expiration);
const canShowUpgradeSock = data.can_show_upgrade_sock;
const certData = camelCaseObject(data.cert_data);
const courseBlocks = data.course_blocks ? normalizeOutlineBlocks(courseId, data.course_blocks.blocks) : {};
const courseGoals = camelCaseObject(data.course_goals);
const courseTools = camelCaseObject(data.course_tools);
const datesBannerInfo = camelCaseObject(data.dates_banner_info);
const datesWidget = camelCaseObject(data.dates_widget);
const enableProctoredExams = data.enable_proctored_exams;
const enrollAlert = camelCaseObject(data.enroll_alert);
const enrollmentMode = data.enrollment_mode;
const handoutsHtml = data.handouts_html;
const hasScheduledContent = data.has_scheduled_content;
const hasEnded = data.has_ended;
const offer = camelCaseObject(data.offer);
const resumeCourse = camelCaseObject(data.resume_course);
const timeOffsetMillis = getTimeOffsetMillis(headers && headers.date, requestTime, responseTime);
const userHasPassingGrade = data.user_has_passing_grade;
const verifiedMode = camelCaseObject(data.verified_mode);
const welcomeMessageHtml = data.welcome_message_html || '';
return {
accessExpiration,
canShowUpgradeSock,
certData,
courseBlocks,
courseGoals,
courseTools,
datesBannerInfo,
datesWidget,
enrollAlert,
enrollmentMode,
enableProctoredExams,
handoutsHtml,
hasScheduledContent,
hasEnded,
offer,
resumeCourse,
timeOffsetMillis, // This should move to a global time correction reference
userHasPassingGrade,
verifiedMode,
welcomeMessageHtml,
};
}
export async function postCourseDeadlines(courseId, model) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`);
return getAuthenticatedHttpClient().post(url.href, {
course_key: courseId,
research_event_data: { location: `${model}-tab` },
});
}
export async function deprecatedPostCourseGoals(courseId, goalKey) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
return getAuthenticatedHttpClient().post(url.href, { course_id: courseId, goal_key: goalKey });
}
export async function postWeeklyLearningGoal(courseId, daysPerWeek, subscribedToReminders) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
return getAuthenticatedHttpClient().post(url.href, {
course_id: courseId,
days_per_week: daysPerWeek,
subscribed_to_reminders: subscribedToReminders,
});
}
export async function postDismissWelcomeMessage(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/dismiss_welcome_message`);
await getAuthenticatedHttpClient().post(url.href, { course_id: courseId });
}
export async function postRequestCert(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/courses/${courseId}/generate_user_cert`);
await getAuthenticatedHttpClient().post(url.href);
}
export async function executePostFromPostEvent(postData, researchEventData) {
const url = new URL(postData.url);
return getAuthenticatedHttpClient().post(url.href, {
course_key: postData.bodyParams.courseId,
research_event_data: researchEventData,
});
}
export async function unsubscribeFromCourseGoal(token) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/unsubscribe_from_course_goal/${token}`);
return getAuthenticatedHttpClient().post(url.href)
.then(res => camelCaseObject(res));
}

View File

@@ -0,0 +1,16 @@
import { getTimeOffsetMillis } from './api';
describe('Calculate the time offset properly', () => {
it('Should return 0 if the headerDate is not set', async () => {
const offset = getTimeOffsetMillis(undefined, undefined, undefined);
expect(offset).toBe(0);
});
it('Should return the offset', async () => {
const headerDate = '2021-04-13T11:01:58.135Z';
const requestTime = new Date('2021-04-12T11:01:57.135Z');
const responseTime = new Date('2021-04-12T11:01:58.635Z');
const offset = getTimeOffsetMillis(headerDate, requestTime, responseTime);
expect(offset).toBe(86398750);
});
});

View File

@@ -0,0 +1,10 @@
export {
fetchDatesTab,
fetchOutlineTab,
fetchProgressTab,
resetDeadlines,
deprecatedSaveCourseGoal,
saveWeeklyLearningGoal,
} from './thunks';
export { reducer } from './slice';

View File

@@ -0,0 +1,220 @@
import path from 'path';
import { mergeConfig, getConfig } from '@edx/frontend-platform';
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import {
getCourseHomeCourseMetadata,
getDatesTabData,
} from '../api';
import { initializeMockApp } from '../../../setupTest';
import {
courseId, dateRegex, opaqueKeysRegex, dateTypeRegex,
} from '../../../pacts/constants';
const {
somethingLike: like, term, boolean, string, eachLike,
} = MatchersV3;
const provider = new PactV3({
consumer: 'frontend-app-learning',
provider: 'lms',
log: path.resolve(process.cwd(), 'src/course-home/data/pact-tests/logs', 'pact.log'),
dir: path.resolve(process.cwd(), 'src/pacts'),
pactfileWriteMode: 'merge',
logLevel: 'DEBUG',
cors: true,
});
describe('Course Home Service', () => {
beforeAll(async () => {
initializeMockApp();
mergeConfig({
LMS_BASE_URL: 'http://localhost:8081',
}, 'Custom app config for pact tests');
});
describe('When a request to fetch tab is made', () => {
it('returns tab data for a course_id', async () => {
setTimeout(() => {
provider.addInteraction({
state: `Tab data exists for course_id ${courseId}`,
uponReceiving: 'a request to fetch tab',
withRequest: {
method: 'GET',
path: `/api/course_home/course_metadata/${courseId}`,
},
willRespondWith: {
status: 200,
body: {
can_show_upgrade_sock: boolean(false),
verified_mode: like({
access_expiration_date: null,
currency: 'USD',
currency_symbol: '$',
price: 149,
sku: '8CF08E5',
upgrade_url: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
}),
celebrations: like({
first_section: false,
streak_length_to_celebrate: null,
streak_discount_enabled: false,
}),
course_access: {
has_access: boolean(true),
error_code: null,
developer_message: null,
user_message: null,
additional_context_user_message: null,
user_fragment: null,
},
course_id: term({
generate: 'course-v1:edX+DemoX+Demo_Course',
matcher: opaqueKeysRegex,
}),
is_enrolled: boolean(true),
is_self_paced: boolean(false),
is_staff: boolean(true),
number: string('DemoX'),
org: string('edX'),
original_user_is_staff: boolean(true),
start: term({
generate: '2013-02-05T05:00:00Z',
matcher: dateRegex,
}),
tabs: eachLike({
tab_id: 'courseware',
title: 'Course',
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
}),
title: string('Demonstration Course'),
username: string('edx'),
},
},
});
const normalizedTabData = {
canShowUpgradeSock: false,
verifiedMode: {
accessExpirationDate: null,
currency: 'USD',
currencySymbol: '$',
price: 149,
sku: '8CF08E5',
upgradeUrl: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
},
celebrations: {
firstSection: false,
streakLengthToCelebrate: null,
streakDiscountEnabled: false,
},
courseAccess: {
hasAccess: true,
errorCode: null,
developerMessage: null,
userMessage: null,
additionalContextUserMessage: null,
userFragment: null,
},
courseId: 'course-v1:edX+DemoX+Demo_Course',
isEnrolled: true,
isMasquerading: false,
isSelfPaced: false,
isStaff: true,
number: 'DemoX',
org: 'edX',
originalUserIsStaff: true,
start: '2013-02-05T05:00:00Z',
tabs: [
{
slug: 'outline',
title: 'Course',
url: `${getConfig().BASE_URL}/course/course-v1:edX+DemoX+Demo_Course/home`,
},
],
title: 'Demonstration Course',
username: 'edx',
};
const response = getCourseHomeCourseMetadata(courseId, 'outline');
expect(response).toBeTruthy();
expect(response).toEqual(normalizedTabData);
}, 100);
});
});
describe('When a request to fetch dates tab is made', () => {
it('returns course date blocks for a course_id', async () => {
setTimeout(() => {
provider.addInteraction({
state: `course date blocks exist for course_id ${courseId}`,
uponReceiving: 'a request to fetch dates tab',
withRequest: {
method: 'GET',
path: `/api/course_home/dates/${courseId}`,
},
willRespondWith: {
status: 200,
body: {
dates_banner_info: like({
missed_deadlines: false,
content_type_gating_enabled: false,
missed_gated_content: false,
verified_upgrade_link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
}),
course_date_blocks: eachLike({
assignment_type: null,
complete: null,
date: term({
generate: '2013-02-05T05:00:00Z',
matcher: dateRegex,
}),
date_type: term({
generate: 'verified-upgrade-deadline',
matcher: dateTypeRegex,
}),
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
learner_has_access: true,
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
link_text: 'Upgrade to Verified Certificate',
title: 'Verification Upgrade Deadline',
extra_info: null,
first_component_block_id: '',
}),
has_ended: boolean(false),
learner_is_full_access: boolean(true),
user_timezone: null,
},
},
});
const camelCaseResponse = {
datesBannerInfo: {
missedDeadlines: false,
contentTypeGatingEnabled: false,
missedGatedContent: false,
verifiedUpgradeLink: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
},
courseDateBlocks: [
{
assignmentType: null,
complete: null,
date: '2013-02-05T05:00:00Z',
dateType: 'verified-upgrade-deadline',
description: 'You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.',
learnerHasAccess: true,
link: `${getConfig().ECOMMERCE_BASE_URL}/basket/add/?sku=8CF08E5`,
linkText: 'Upgrade to Verified Certificate',
title: 'Verification Upgrade Deadline',
extraInfo: null,
firstComponentBlockId: '',
},
],
hasEnded: false,
learnerIsFullAccess: true,
userTimezone: null,
};
const response = getDatesTabData(courseId);
expect(response).toBeTruthy();
expect(response).toEqual(camelCaseResponse);
}, 100);
});
});
});

View File

@@ -0,0 +1,232 @@
import { Factory } from 'rosie';
import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';
import * as thunks from './thunks';
import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils';
import { initializeMockApp } from '../../setupTest';
import initializeStore from '../../store';
const { loggingService } = initializeMockApp();
const axiosMock = new MockAdapter(getAuthenticatedHttpClient());
describe('Data layer integration tests', () => {
const courseHomeMetadata = Factory.build('courseHomeMetadata');
const { id: courseId } = courseHomeMetadata;
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`;
courseMetadataUrl = appendBrowserTimezoneToUrl(courseMetadataUrl);
const courseHomeAccessDeniedMetadata = Factory.build(
'courseHomeMetadata',
{
id: courseId,
course_access: {
has_access: false,
error_code: 'bad codes',
additional_context_user_message: 'your Codes Are BAD',
},
},
);
let store;
beforeEach(() => {
axiosMock.reset();
loggingService.logError.mockReset();
store = initializeStore();
});
describe('Test fetchDatesTab', () => {
const datesBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/dates`;
it('Should fail to fetch if error occurs', async () => {
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(`${datesBaseUrl}/${courseId}`).networkError();
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const datesTabData = Factory.build('datesTabData');
const datesUrl = `${datesBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(datesUrl).reply(200, datesTabData);
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
});
it.each([401, 403, 404])(
'should result in fetch denied for expected errors and failed for all others',
async (errorStatus) => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata);
axiosMock.onGet(`${datesBaseUrl}/${courseId}`).reply(errorStatus, {});
await executeThunk(thunks.fetchDatesTab(courseId), store.dispatch);
let expectedState = 'failed';
if (errorStatus === 401 || errorStatus === 403) {
expectedState = 'denied';
}
expect(store.getState().courseHome.courseStatus).toEqual(expectedState);
},
);
});
describe('Test fetchOutlineTab', () => {
const outlineBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/outline`;
const outlineUrl = `${outlineBaseUrl}/${courseId}`;
it('Should result in fetch failure if error occurs', async () => {
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(outlineUrl).networkError();
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const outlineTabData = Factory.build('outlineTabData', { courseId });
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(outlineUrl).reply(200, outlineTabData);
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
});
it.each([401, 403, 404])(
'should result in fetch denied for expected errors and failed for all others',
async (errorStatus) => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata);
axiosMock.onGet(outlineUrl).reply(errorStatus, {});
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
let expectedState = 'failed';
if (errorStatus === 403) {
expectedState = 'denied';
}
expect(store.getState().courseHome.courseStatus).toEqual(expectedState);
},
);
});
describe('Test fetchProgressTab', () => {
const progressBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/progress`;
it('Should result in fetch failure if error occurs', async () => {
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(`${progressBaseUrl}/${courseId}`).networkError();
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const progressTabData = Factory.build('progressTabData', { courseId });
const progressUrl = `${progressBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(progressUrl).reply(200, progressTabData);
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
});
it('Should handle the url including a targetUserId', async () => {
const progressTabData = Factory.build('progressTabData', { courseId });
const targetUserId = 2;
const progressUrl = `${progressBaseUrl}/${courseId}/${targetUserId}/`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(progressUrl).reply(200, progressTabData);
await executeThunk(thunks.fetchProgressTab(courseId, 2), store.dispatch);
const state = store.getState();
expect(state.courseHome.targetUserId).toEqual(2);
});
it.each([401, 403, 404])(
'should result in fetch denied for expected errors and failed for all others',
async (errorStatus) => {
const progressUrl = `${progressBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeAccessDeniedMetadata);
axiosMock.onGet(progressUrl).reply(errorStatus, {});
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
expect(store.getState().courseHome.courseStatus).toEqual('denied');
},
);
});
describe('Test saveCourseGoal', () => {
it('Should save course goal', async () => {
const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`;
axiosMock.onPost(goalUrl).reply(200, {});
await thunks.deprecatedSaveCourseGoal(courseId, 'unsure');
expect(axiosMock.history.post[0].url).toEqual(goalUrl);
expect(axiosMock.history.post[0].data).toEqual(`{"course_id":"${courseId}","goal_key":"unsure"}`);
});
});
describe('Test resetDeadlines', () => {
it('Should reset course deadlines', async () => {
const resetUrl = `${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`;
const model = 'dates';
axiosMock.onPost(resetUrl).reply(201, {});
const getTabDataMock = jest.fn(() => ({
type: 'MOCK_ACTION',
}));
await executeThunk(thunks.resetDeadlines(courseId, model, getTabDataMock), store.dispatch);
expect(axiosMock.history.post[0].url).toEqual(resetUrl);
expect(axiosMock.history.post[0].data).toEqual(`{"course_key":"${courseId}","research_event_data":{"location":"dates-tab"}}`);
expect(getTabDataMock).toHaveBeenCalledWith(courseId);
});
});
describe('Test dismissWelcomeMessage', () => {
it('Should dismiss welcome message', async () => {
const dismissUrl = `${getConfig().LMS_BASE_URL}/api/course_home/dismiss_welcome_message`;
axiosMock.onPost(dismissUrl).reply(201);
await executeThunk(thunks.dismissWelcomeMessage(courseId), store.dispatch);
expect(axiosMock.history.post[0].url).toEqual(dismissUrl);
expect(axiosMock.history.post[0].data).toEqual(`{"course_id":"${courseId}"}`);
});
});
});

View File

@@ -0,0 +1,64 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
export const LOADING = 'loading';
export const LOADED = 'loaded';
export const FAILED = 'failed';
export const DENIED = 'denied';
const slice = createSlice({
name: 'course-home',
initialState: {
courseStatus: 'loading',
courseId: null,
proctoringPanelStatus: 'loading',
toastBodyText: null,
toastBodyLink: null,
toastHeader: '',
},
reducers: {
fetchProctoringInfoResolved: (state) => {
state.proctoringPanelStatus = LOADED;
},
fetchTabDenied: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = DENIED;
},
fetchTabFailure: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = FAILED;
},
fetchTabRequest: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = LOADING;
},
fetchTabSuccess: (state, { payload }) => {
state.courseId = payload.courseId;
state.targetUserId = payload.targetUserId;
state.courseStatus = LOADED;
},
setCallToActionToast: (state, { payload }) => {
const {
header,
link,
linkText,
} = payload;
state.toastBodyLink = link;
state.toastBodyText = linkText;
state.toastHeader = header;
},
},
});
export const {
fetchProctoringInfoResolved,
fetchTabDenied,
fetchTabFailure,
fetchTabRequest,
fetchTabSuccess,
setCallToActionToast,
} = slice.actions;
export const {
reducer,
} = slice;

View File

@@ -0,0 +1,141 @@
import { logError } from '@edx/frontend-platform/logging';
import { camelCaseObject } from '@edx/frontend-platform';
import {
executePostFromPostEvent,
getCourseHomeCourseMetadata,
getDatesTabData,
getOutlineTabData,
getProgressTabData,
postCourseDeadlines,
deprecatedPostCourseGoals,
postWeeklyLearningGoal,
postDismissWelcomeMessage,
postRequestCert,
getLiveTabIframe,
} from './api';
import {
addModel,
} from '../../generic/model-store';
import {
fetchTabDenied,
fetchTabFailure,
fetchTabRequest,
fetchTabSuccess,
setCallToActionToast,
} from './slice';
const eventTypes = {
POST_EVENT: 'post_event',
};
export function fetchTab(courseId, tab, getTabData, targetUserId) {
return async (dispatch) => {
dispatch(fetchTabRequest({ courseId }));
try {
const courseHomeCourseMetadata = await getCourseHomeCourseMetadata(courseId, 'outline');
dispatch(addModel({
modelType: 'courseHomeMeta',
model: {
id: courseId,
...courseHomeCourseMetadata,
},
}));
const tabDataResult = getTabData && await getTabData(courseId, targetUserId);
if (tabDataResult) {
dispatch(addModel({
modelType: tab,
model: {
id: courseId,
...tabDataResult,
},
}));
}
// Disable the access-denied path for now - it caused a regression
if (!courseHomeCourseMetadata.courseAccess.hasAccess) {
dispatch(fetchTabDenied({ courseId }));
} else if (tabDataResult || !getTabData) {
dispatch(fetchTabSuccess({
courseId,
targetUserId,
}));
}
} catch (e) {
dispatch(fetchTabFailure({ courseId }));
logError(e);
}
};
}
export function fetchDatesTab(courseId) {
return fetchTab(courseId, 'dates', getDatesTabData);
}
export function fetchProgressTab(courseId, targetUserId) {
return fetchTab(courseId, 'progress', getProgressTabData, parseInt(targetUserId, 10) || targetUserId);
}
export function fetchOutlineTab(courseId) {
return fetchTab(courseId, 'outline', getOutlineTabData);
}
export function fetchLiveTab(courseId) {
return fetchTab(courseId, 'live', getLiveTabIframe);
}
export function fetchDiscussionTab(courseId) {
return fetchTab(courseId, 'discussion');
}
export function dismissWelcomeMessage(courseId) {
return async () => postDismissWelcomeMessage(courseId);
}
export function requestCert(courseId) {
return async () => postRequestCert(courseId);
}
export function resetDeadlines(courseId, model, getTabData) {
return async (dispatch) => {
postCourseDeadlines(courseId, model).then(response => {
const { data } = response;
const {
header,
link,
link_text: linkText,
} = data;
dispatch(getTabData(courseId));
dispatch(setCallToActionToast({ header, link, linkText }));
});
};
}
export async function deprecatedSaveCourseGoal(courseId, goalKey) {
return deprecatedPostCourseGoals(courseId, goalKey);
}
export async function saveWeeklyLearningGoal(courseId, daysPerWeek, subscribedToReminders) {
return postWeeklyLearningGoal(courseId, daysPerWeek, subscribedToReminders);
}
export function processEvent(eventData, getTabData) {
return async (dispatch) => {
// Pulling this out early so the data doesn't get camelCased and is easier
// to use when it's passed to the backend
const { research_event_data: researchEventData } = eventData;
const event = camelCaseObject(eventData);
if (event.eventName === eventTypes.POST_EVENT) {
executePostFromPostEvent(event.postData, researchEventData).then(response => {
const { data } = response;
const {
header,
link,
link_text: linkText,
} = data;
dispatch(getTabData(event.postData.bodyParams.courseId));
dispatch(setCallToActionToast({ header, link, linkText }));
});
}
};
}

View File

@@ -0,0 +1,66 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import Timeline from './timeline/Timeline';
import { fetchDatesTab } from '../data';
import { useModel } from '../../generic/model-store';
import SuggestedScheduleHeader from '../suggested-schedule-messaging/SuggestedScheduleHeader';
import ShiftDatesAlert from '../suggested-schedule-messaging/ShiftDatesAlert';
import UpgradeToCompleteAlert from '../suggested-schedule-messaging/UpgradeToCompleteAlert';
import UpgradeToShiftDatesAlert from '../suggested-schedule-messaging/UpgradeToShiftDatesAlert';
const DatesTab = ({ intl }) => {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
isSelfPaced,
org,
} = useModel('courseHomeMeta', courseId);
const {
courseDateBlocks,
} = useModel('dates', courseId);
const hasDeadlines = courseDateBlocks && courseDateBlocks.some(x => x.dateType === 'assignment-due-date');
const logUpgradeLinkClick = () => {
sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', {
org_key: org,
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
};
return (
<>
<div role="heading" aria-level="1" className="h2 my-3">
{intl.formatMessage(messages.title)}
</div>
{isSelfPaced && hasDeadlines && (
<>
<ShiftDatesAlert model="dates" fetch={fetchDatesTab} />
<SuggestedScheduleHeader />
<UpgradeToCompleteAlert logUpgradeLinkClick={logUpgradeLinkClick} />
<UpgradeToShiftDatesAlert logUpgradeLinkClick={logUpgradeLinkClick} model="dates" />
</>
)}
<Timeline />
</>
);
};
DatesTab.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(DatesTab);

View File

@@ -0,0 +1,352 @@
import React from 'react';
import { Route } from 'react-router';
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getConfig, history } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { waitForElementToBeRemoved } from '@testing-library/dom';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import DatesTab from './DatesTab';
import { fetchDatesTab } from '../data';
import { fireEvent, initializeMockApp, waitFor } from '../../setupTest';
import initializeStore from '../../store';
import { TabContainer } from '../../tab-page';
import { appendBrowserTimezoneToUrl } from '../../utils';
import { UserMessagesProvider } from '../../generic/user-messages';
initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
describe('DatesTab', () => {
let axiosMock;
let store;
let component;
beforeEach(() => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore();
component = (
<AppProvider store={store}>
<UserMessagesProvider>
<Route path="/course/:courseId/dates">
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
<DatesTab />
</TabContainer>
</Route>
</UserMessagesProvider>
</AppProvider>
);
});
const datesTabData = Factory.build('datesTabData');
let courseMetadata = Factory.build('courseHomeMetadata', { user_timezone: 'America/New_York' });
const { id: courseId } = courseMetadata;
const datesUrl = `${getConfig().LMS_BASE_URL}/api/course_home/dates/${courseId}`;
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`;
courseMetadataUrl = appendBrowserTimezoneToUrl(courseMetadataUrl);
function setMetadata(attributes, options) {
courseMetadata = Factory.build('courseHomeMetadata', attributes, options);
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
}
// The dates tab is largely repetitive non-interactive static data. Thus it's a little tough to follow
// testing-library's advice around testing the way your user uses the site (i.e. can't find form elements by label or
// anything). Instead, we find elements by printed date (which is what the user sees) and data-testid. Which is
// better than assuming anything about how the surrounding elements are organized by div and span or whatever. And
// better than adding non-style class names.
// Hence the following getDay query helper.
async function getDay(date) {
const dateNode = await screen.findByText(date);
let parent = dateNode.parentElement;
while (parent) {
if (parent.dataset && parent.dataset.testid === 'dates-day') {
return {
day: parent,
header: within(parent).getByTestId('dates-header'),
items: within(parent).queryAllByTestId('dates-item'),
};
}
parent = parent.parentElement;
}
throw new Error('Did not find day container');
}
describe('when receiving a full set of dates data', () => {
beforeEach(() => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
axiosMock.onGet(datesUrl).reply(200, datesTabData);
history.push(`/course/${courseId}/dates`); // so tab can pull course id from url
render(component);
});
it('handles unreleased & complete', async () => {
const { header } = await getDay('Sun, May 3, 2020');
const badges = within(header).getAllByTestId('dates-badge');
expect(badges).toHaveLength(2);
expect(badges[0]).toHaveTextContent('Completed');
expect(badges[1]).toHaveTextContent('Not yet released');
});
it('handles unreleased & past due', async () => {
const { header } = await getDay('Mon, May 4, 2020');
const badges = within(header).getAllByTestId('dates-badge');
expect(badges).toHaveLength(2);
expect(badges[0]).toHaveTextContent('Past due');
expect(badges[1]).toHaveTextContent('Not yet released');
});
it('handles verified only', async () => {
const { day } = await getDay('Sun, Aug 18, 2030');
const badge = within(day).getByTestId('dates-badge');
expect(badge).toHaveTextContent('Verified only');
});
it('verified only has no link', async () => {
const { day } = await getDay('Sun, Aug 18, 2030');
expect(within(day).queryByRole('link')).toBeNull();
});
it('same status items have header badge', async () => {
const { day, header } = await getDay('Tue, May 26, 2020');
const badge = within(header).getByTestId('dates-badge');
expect(badge).toHaveTextContent('Past due'); // one header badge
expect(within(day).getAllByTestId('dates-badge')).toHaveLength(1); // no other badges
});
it('different status items have individual badges', async () => {
const { header, items } = await getDay('Thu, May 28, 2020');
const headerBadges = within(header).queryAllByTestId('dates-badge');
expect(headerBadges).toHaveLength(0); // no header badges
expect(items).toHaveLength(2);
expect(within(items[0]).getByTestId('dates-badge')).toHaveTextContent('Completed');
expect(within(items[1]).getByTestId('dates-badge')).toHaveTextContent('Past due');
});
it('shows extra info', async () => {
const { items } = await getDay('Sat, Aug 17, 2030');
expect(items).toHaveLength(3);
const tipIcon = within(items[2]).getByTestId('dates-extra-info');
const tipText = "ORA Dates are set by the instructor, and can't be changed";
expect(screen.queryByText(tipText)).toBeNull(); // tooltip does not start in DOM
userEvent.hover(tipIcon);
const tooltip = screen.getByText(tipText); // now it's there
userEvent.unhover(tipIcon);
await waitForElementToBeRemoved(tooltip); // and it's gone again
});
});
describe('Suggested schedule messaging', () => {
beforeEach(() => {
setMetadata({ is_self_paced: true, is_enrolled: true });
history.push(`/course/${courseId}/dates`);
});
it('renders SuggestedScheduleHeader', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: false,
missedDeadlines: false,
missedGatedContent: false,
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
await waitFor(() => expect(screen.getByText('Weve built a suggested schedule to help you stay on track. But dont worry—its flexible so you can learn at your own pace.')).toBeInTheDocument());
});
it('renders UpgradeToCompleteAlert', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
await waitFor(() => expect(screen.getByText('You are auditing this course, which means that you are unable to participate in graded assignments. To complete graded assignments as part of this course, you can upgrade today.')).toBeInTheDocument());
expect(screen.getByRole('button', { name: 'Upgrade now' })).toBeInTheDocument();
});
it('renders UpgradeToShiftDatesAlert', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: true,
missedGatedContent: true,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
await waitFor(() => expect(screen.getByText('It looks like you missed some important deadlines based on our suggested schedule.')).toBeInTheDocument());
expect(screen.getByText('To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Dont worry—you wont lose any of the progress youve made when you shift your due dates.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Upgrade to shift due dates' })).toBeInTheDocument();
});
it('renders ShiftDatesAlert', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: true,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
await waitFor(() => expect(screen.getByText('It looks like you missed some important deadlines based on our suggested schedule.')).toBeInTheDocument());
expect(screen.getByText('To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Dont worry—you wont lose any of the progress youve made when you shift your due dates.')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Shift due dates' })).toBeInTheDocument();
});
it('handles shift due dates click', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: true,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
// confirm "Shift due dates" button has rendered
await waitFor(() => expect(screen.getByRole('button', { name: 'Shift due dates' })).toBeInTheDocument());
// update response to reflect shifted dates
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
const resetDeadlinesData = {
header: "You've successfully shifted your dates!",
};
axiosMock.onPost(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`).reply(200, resetDeadlinesData);
// click "Shift due dates"
fireEvent.click(screen.getByRole('button', { name: 'Shift due dates' }));
// wait for page to reload & Toast to render
await waitFor(() => expect(screen.getByText("You've successfully shifted your dates!")).toBeInTheDocument());
// confirm "Shift due dates" button has not rendered
expect(screen.queryByRole('button', { name: 'Shift due dates' })).not.toBeInTheDocument();
});
it('sends analytics event onClick of upgrade button in UpgradeToCompleteAlert', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
const upgradeButton = await waitFor(() => screen.getByRole('button', { name: 'Upgrade now' }));
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
});
it('sends analytics event onClick of upgrade button in UpgradeToShiftDatesAlert', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: true,
missedGatedContent: true,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);
const upgradeButton = await waitFor(() => screen.getByRole('button', { name: 'Upgrade to shift due dates' }));
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
});
});
describe('when receiving an access denied error', () => {
// These tests could go into any particular tab, as they all go through the same flow. But dates tab works.
async function renderDenied(errorCode) {
setMetadata({
course_access: {
has_access: false,
error_code: errorCode,
additional_context_user_message: 'uhoh oh no', // only used by audit_expired
},
});
render(component);
await waitForElementToBeRemoved(screen.getByRole('status'));
}
beforeEach(() => {
axiosMock.onGet(datesUrl).reply(200, datesTabData);
history.push(`/course/${courseId}/dates`); // so tab can pull course id from url
});
it('redirects to course survey for a survey_required error code', async () => {
await renderDenied('survey_required');
expect(global.location.href).toEqual(`http://localhost/redirect/survey/${courseMetadata.id}`);
});
it('redirects to dashboard for an unfulfilled_milestones error code', async () => {
await renderDenied('unfulfilled_milestones');
expect(global.location.href).toEqual('http://localhost/redirect/dashboard');
});
it('redirects to the dashboard with an attached access_response_error for an audit_expired error code', async () => {
await renderDenied('audit_expired');
expect(global.location.href).toEqual('http://localhost/redirect/dashboard?access_response_error=uhoh%20oh%20no');
});
it('redirects to the dashboard with a notlive start date for a course_not_started error code', async () => {
await renderDenied('course_not_started');
expect(global.location.href).toEqual('http://localhost/redirect/dashboard?notlive=2/5/2013'); // date from factory
});
it('redirects to the home page when unauthenticated', async () => {
await renderDenied('authentication_required');
expect(global.location.href).toEqual(`http://localhost/course/${courseMetadata.id}/home`);
});
it('redirects to the home page when unenrolled', async () => {
await renderDenied('enrollment_required');
expect(global.location.href).toEqual(`http://localhost/course/${courseMetadata.id}/home`);
});
});
});

View File

@@ -0,0 +1,3 @@
import DatesTab from './DatesTab';
export default DatesTab;

View File

@@ -0,0 +1,41 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
completed: {
id: 'learning.dates.badge.completed',
defaultMessage: 'Completed',
description: 'shown as label for the assignments which learner has completed.',
},
dueNext: {
id: 'learning.dates.badge.dueNext',
defaultMessage: 'Due next',
description: 'Shown as label for the assignment which date is in the future',
},
pastDue: {
id: 'learning.dates.badge.pastDue',
defaultMessage: 'Past due',
description: 'Shown as label for the assignments which deadline has passed',
},
title: {
id: 'learning.dates.title',
defaultMessage: 'Important dates',
description: 'The title of dates tab (course timeline).',
},
today: {
id: 'learning.dates.badge.today',
defaultMessage: 'Today',
description: 'Label used when the scheduled date for the assignment matches the current day',
},
unreleased: {
id: 'learning.dates.badge.unreleased',
defaultMessage: 'Not yet released',
description: 'Shown as label for assignments which date is unknown yet',
},
verifiedOnly: {
id: 'learning.dates.badge.verifiedOnly',
defaultMessage: 'Verified only',
description: 'Shown as label for assignments which learner has no access to.',
},
});
export default messages;

View File

@@ -0,0 +1,129 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import {
FormattedDate,
FormattedTime,
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Tooltip, OverlayTrigger } from '@edx/paragon';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useModel } from '../../../generic/model-store';
import { getBadgeListAndColor } from './badgelist';
import { isLearnerAssignment } from '../utils';
const Day = ({
date,
first,
intl,
items,
last,
}) => {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
userTimezone,
} = useModel('courseHomeMeta', courseId);
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
const { color, badges } = getBadgeListAndColor(date, intl, null, items);
return (
<li className="dates-day pb-4" data-testid="dates-day">
{/* Top Line */}
{!first && <div className="dates-line-top border-1 border-left border-gray-900 bg-gray-900" />}
{/* Dot */}
<div className={classNames(color, 'dates-dot border border-gray-900')} />
{/* Bottom Line */}
{!last && <div className="dates-line-bottom border-1 border-left border-gray-900 bg-gray-900" />}
{/* Content */}
<div className="d-inline-block ml-3 pl-2">
<div className="row w-100 m-0 mb-1 align-items-center text-primary-700" data-testid="dates-header">
<FormattedDate
value={date}
day="numeric"
month="short"
weekday="short"
year="numeric"
{...timezoneFormatArgs}
/>
{badges}
</div>
{items.map((item) => {
const { badges: itemBadges } = getBadgeListAndColor(date, intl, item, items);
const showDueDateTime = item.dateType === 'assignment-due-date';
const showLink = item.link && isLearnerAssignment(item);
const title = showLink ? (<u><a href={item.link} className="text-reset">{item.title}</a></u>) : item.title;
const available = item.learnerHasAccess && (item.link || !isLearnerAssignment(item));
const textColor = available ? 'text-primary-700' : 'text-gray-500';
return (
<div key={item.title + item.date} className={classNames(textColor, 'small pb-1')} data-testid="dates-item">
<div>
<span className="small">
<span className="font-weight-bold">{item.assignmentType && `${item.assignmentType}: `}{title}</span>
{showDueDateTime && (
<span>
<span className="mx-1">due</span>
<FormattedTime
value={date}
timeZoneName="short"
{...timezoneFormatArgs}
/>
</span>
)}
</span>
{itemBadges}
{item.extraInfo && (
<OverlayTrigger
placement="bottom"
overlay={
<Tooltip>{item.extraInfo}</Tooltip>
}
>
<FontAwesomeIcon icon={faInfoCircle} className="fa-xs ml-1 text-gray-700" data-testid="dates-extra-info" />
</OverlayTrigger>
)}
</div>
{item.description && <div className="small mb-2">{item.description}</div>}
</div>
);
})}
</div>
</li>
);
};
Day.propTypes = {
date: PropTypes.objectOf(Date).isRequired,
first: PropTypes.bool,
intl: intlShape.isRequired,
items: PropTypes.arrayOf(PropTypes.shape({
date: PropTypes.string,
dateType: PropTypes.string,
description: PropTypes.string,
dueNext: PropTypes.bool,
learnerHasAccess: PropTypes.bool,
link: PropTypes.string,
title: PropTypes.string,
})).isRequired,
last: PropTypes.bool,
};
Day.defaultProps = {
first: false,
last: false,
};
export default injectIntl(Day);

View File

@@ -0,0 +1,47 @@
$dot-radius: 0.3rem;
$dot-size: $dot-radius * 2;
$offset: $dot-radius * 1.5;
.dates-day {
position: relative;
}
.dates-line-top {
display: inline-block;
position: absolute;
left: $offset;
top: 0;
height: $offset;
z-index: 0;
}
.dates-dot {
display: inline-block;
position: absolute;
border-radius: 50%;
left: $dot-radius * 0.5; // save room for today's larger size
top: $offset;
height: $dot-size;
width: $dot-size;
z-index: 1;
&.dates-bg-today {
left: 0;
top: $offset - $dot-radius;
height: $dot-size * 1.5;
width: $dot-size * 1.5;
}
}
.dates-line-bottom {
display: inline-block;
position: absolute;
top: $offset + $dot-size;
bottom: 0;
left: $offset;
z-index: 0;
}
.dates-bg-today {
background: #ffdb87;
}

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { useModel } from '../../../generic/model-store';
import Day from './Day';
import { daycmp, isLearnerAssignment } from '../utils';
const Timeline = () => {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
courseDateBlocks,
} = useModel('dates', courseId);
// Group date items by day (assuming they are sorted in first place) and add some metadata
const groupedDates = [];
const now = new Date();
let foundNextDue = false;
let foundToday = false;
courseDateBlocks.forEach(courseDateBlock => {
const dateInfo = { ...courseDateBlock };
const parsedDate = new Date(dateInfo.date);
if (!foundNextDue && parsedDate >= now && isLearnerAssignment(dateInfo) && !dateInfo.complete) {
foundNextDue = true;
dateInfo.dueNext = true;
}
if (!foundToday) {
const compared = daycmp(parsedDate, now);
if (compared === 0) {
foundToday = true;
} else if (compared > 0) {
foundToday = true;
groupedDates.push({
date: now,
items: [],
});
}
}
if (groupedDates.length === 0 || daycmp(groupedDates[groupedDates.length - 1].date, parsedDate) !== 0) {
// Add new grouped date
groupedDates.push({
date: parsedDate,
items: [dateInfo],
first: groupedDates.length === 0,
});
} else {
groupedDates[groupedDates.length - 1].items.push(dateInfo);
}
});
if (!foundToday) {
groupedDates.push({ date: now, items: [] });
}
if (groupedDates.length) {
groupedDates[groupedDates.length - 1].last = true;
}
return (
<ul className="list-unstyled m-0 mt-4 pt-2">
{groupedDates.map((groupedDate) => (
<Day key={groupedDate.date} {...groupedDate} />
))}
</ul>
);
};
export default Timeline;

View File

@@ -0,0 +1,118 @@
import React from 'react';
import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLock } from '@fortawesome/free-solid-svg-icons';
import { Badge } from '@edx/paragon';
import messages from '../messages';
import { daycmp, isLearnerAssignment } from '../utils';
function hasAccess(item) {
return item.learnerHasAccess;
}
function isComplete(assignment) {
return assignment.complete;
}
function isPastDue(assignment) {
return !isComplete(assignment) && (new Date(assignment.date) < new Date());
}
function isUnreleased(assignment) {
return !assignment.link;
}
// Pass a null item if you want to get a whole day's badge list, not just one item's list.
// Returns an object with 'color' and 'badges' properties.
function getBadgeListAndColor(date, intl, item, items) {
const now = new Date();
const assignments = items.filter(isLearnerAssignment);
const isToday = daycmp(date, now) === 0;
const isInFuture = daycmp(date, now) > 0;
// This badge info list is in order of priority (they will appear left to right in this order and the first badge
// sets the color of the dot in the timeline).
const badgesInfo = [
{
message: messages.today,
shownForDay: isToday,
bg: 'bg-warning-300',
className: 'text-black',
},
{
message: messages.completed,
shownForDay: assignments.length && assignments.every(isComplete),
shownForItem: x => isLearnerAssignment(x) && isComplete(x),
bg: 'bg-light-500',
className: 'text-black',
},
{
message: messages.pastDue,
shownForDay: assignments.length && assignments.every(isPastDue),
shownForItem: x => isLearnerAssignment(x) && isPastDue(x),
bg: 'bg-dark-200',
className: 'text-white',
},
{
message: messages.dueNext,
shownForDay: !isToday && assignments.some(x => x.dueNext),
shownForItem: x => x.dueNext,
bg: 'bg-gray-500',
className: 'text-white',
},
{
message: messages.unreleased,
shownForDay: assignments.length && assignments.every(isUnreleased),
shownForItem: x => isLearnerAssignment(x) && isUnreleased(x),
className: 'border border-gray-500 text-gray-500',
},
{
message: messages.verifiedOnly,
shownForDay: items.length && items.every(x => !hasAccess(x)),
shownForItem: x => !hasAccess(x),
icon: faLock,
bg: 'bg-dark-700',
className: 'text-white',
},
];
let color = null; // first color of any badge
const badges = (
<>
{badgesInfo.map(b => {
let shown = b.shownForDay;
if (item) {
if (b.shownForDay) {
shown = false; // don't double up, if the day already has this badge
} else {
shown = b.shownForItem && b.shownForItem(item);
}
}
if (!shown) {
return null;
}
if (!color && !isInFuture) {
color = b.bg;
}
return (
<Badge key={b.message.id} className={classNames('ml-2', b.bg, b.className)} data-testid="dates-badge">
{b.icon && <FontAwesomeIcon icon={b.icon} className="mr-1" />}
{intl.formatMessage(b.message)}
</Badge>
);
})}
</>
);
if (!color && isInFuture) {
color = 'bg-gray-900';
}
return {
color,
badges,
};
}
// eslint-disable-next-line import/prefer-default-export
export { getBadgeListAndColor };

View File

@@ -0,0 +1,16 @@
function daycmp(a, b) {
if (a.getFullYear() < b.getFullYear()) { return -1; }
if (a.getFullYear() > b.getFullYear()) { return 1; }
if (a.getMonth() < b.getMonth()) { return -1; }
if (a.getMonth() > b.getMonth()) { return 1; }
if (a.getDate() < b.getDate()) { return -1; }
if (a.getDate() > b.getDate()) { return 1; }
return 0;
}
// item is a date block returned from the API
function isLearnerAssignment(item) {
return item.learnerHasAccess && item.dateType === 'assignment-due-date';
}
export { daycmp, isLearnerAssignment };

View File

@@ -0,0 +1,36 @@
import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { generatePath, useHistory } from 'react-router';
import { useParams } from 'react-router-dom';
import { useIFrameHeight, useIFramePluginEvents } from '../../generic/hooks';
const DiscussionTab = () => {
const { courseId } = useSelector(state => state.courseHome);
const { path } = useParams();
const [originalPath] = useState(path);
const history = useHistory();
const [, iFrameHeight] = useIFrameHeight();
useIFramePluginEvents({
'discussions.navigate': (payload) => {
const basePath = generatePath('/course/:courseId/discussion', { courseId });
history.push(`${basePath}/${payload.path}`);
},
});
const discussionsUrl = `${getConfig().DISCUSSIONS_MFE_BASE_URL}/${courseId}/${originalPath}`;
return (
<iframe
src={discussionsUrl}
className="d-flex w-100 border-0"
height={iFrameHeight}
style={{ minHeight: '60rem' }}
title="discussion"
/>
);
};
DiscussionTab.propTypes = {};
export default injectIntl(DiscussionTab);

View File

@@ -0,0 +1,61 @@
import { getConfig, history } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { render } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import React from 'react';
import { Route } from 'react-router';
import { Factory } from 'rosie';
import { UserMessagesProvider } from '../../generic/user-messages';
import {
initializeMockApp, messageEvent, screen, waitFor,
} from '../../setupTest';
import initializeStore from '../../store';
import { TabContainer } from '../../tab-page';
import { appendBrowserTimezoneToUrl } from '../../utils';
import { fetchDiscussionTab } from '../data/thunks';
import DiscussionTab from './DiscussionTab';
initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
describe('DiscussionTab', () => {
let axiosMock;
let store;
let component;
beforeEach(() => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore();
component = (
<AppProvider store={store}>
<UserMessagesProvider>
<Route path="/course/:courseId/discussion">
<TabContainer tab="discussion" fetch={fetchDiscussionTab} slice="courseHome">
<DiscussionTab />
</TabContainer>
</Route>
</UserMessagesProvider>
</AppProvider>
);
});
const courseMetadata = Factory.build('courseHomeMetadata', { user_timezone: 'America/New_York' });
const { id: courseId } = courseMetadata;
let courseMetadataUrl = `${getConfig().LMS_BASE_URL}/api/course_home/course_metadata/${courseId}`;
courseMetadataUrl = appendBrowserTimezoneToUrl(courseMetadataUrl);
beforeEach(() => {
axiosMock.onGet(courseMetadataUrl).reply(200, courseMetadata);
history.push(`/course/${courseId}/discussion`); // so tab can pull course id from url
render(component);
});
it('resizes when it gets a size hint from iframe', async () => {
window.postMessage({ ...messageEvent, payload: { height: 1234 } }, '*');
await waitFor(() => expect(screen.getByTitle('discussion'))
.toHaveAttribute('height', String(1234)));
});
});

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import PageLoading from '../../generic/PageLoading';
import { unsubscribeFromCourseGoal } from '../data/api';
import messages from './messages';
import ResultPage from './ResultPage';
const GoalUnsubscribe = ({ intl }) => {
const { token } = useParams();
const [error, setError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState({});
// We don't need to bother with redux for this simple page. We're not sharing state with other pages at all.
useEffect(() => {
unsubscribeFromCourseGoal(token)
.then(
(result) => {
setIsLoading(false);
setData(result.data);
},
() => {
setIsLoading(false);
setError(true);
},
);
// We unfortunately have no information about the user, course, org, or really anything
// as visiting this page is allowed to be done anonymously and without the context of the course.
// The token can be used to connect a user and course, it will just require some post-processing
sendTrackEvent('edx.ui.lms.goal.unsubscribe', { token });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // deps=[] to only run once
return (
<>
<Header showUserDropdown={false} />
<main id="main-content" className="container my-5 text-center">
{isLoading && (
<PageLoading srMessage={`${intl.formatMessage(messages.loading)}`} />
)}
{!isLoading && (
<ResultPage error={error} courseTitle={data.courseTitle} />
)}
</main>
</>
);
};
GoalUnsubscribe.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(GoalUnsubscribe);

View File

@@ -0,0 +1,62 @@
import React from 'react';
import { Route } from 'react-router';
import MockAdapter from 'axios-mock-adapter';
import { getConfig, history } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { render, screen } from '@testing-library/react';
import GoalUnsubscribe from './GoalUnsubscribe';
import { act, initializeMockApp } from '../../setupTest';
import initializeStore from '../../store';
import { UserMessagesProvider } from '../../generic/user-messages';
initializeMockApp();
jest.mock('@edx/frontend-platform/analytics');
describe('GoalUnsubscribe', () => {
let axiosMock;
let store;
let component;
const unsubscribeUrl = `${getConfig().LMS_BASE_URL}/api/course_home/unsubscribe_from_course_goal/TOKEN`;
beforeEach(() => {
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore();
component = (
<AppProvider store={store}>
<UserMessagesProvider>
<Route path="/goal-unsubscribe/:token" component={GoalUnsubscribe} />
</UserMessagesProvider>
</AppProvider>
);
history.push('/goal-unsubscribe/TOKEN'); // so we can pull token from url
});
it('starts with a spinner', () => {
render(component);
expect(screen.getByRole('status')).toBeInTheDocument();
});
it('loads a real token', async () => {
const response = { course_title: 'My Sample Course' };
axiosMock.onPost(unsubscribeUrl).reply(200, response);
await act(async () => render(component));
expect(screen.getByText('Youve unsubscribed from goal reminders')).toBeInTheDocument();
expect(screen.getByText(/your goal for My Sample Course/)).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Go to dashboard' }))
.toHaveAttribute('href', 'http://localhost:18000/dashboard');
});
it('loads a bad token with an error page', async () => {
axiosMock.onPost(unsubscribeUrl).reply(404, {});
await act(async () => render(component));
expect(screen.getByText('Something went wrong')).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Go to dashboard' }))
.toHaveAttribute('href', 'http://localhost:18000/dashboard');
expect(screen.getByRole('link', { name: 'contact support' }))
.toHaveAttribute('href', 'http://localhost:18000/contact');
});
});

View File

@@ -0,0 +1,60 @@
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Hyperlink } from '@edx/paragon';
import messages from './messages';
import { ReactComponent as UnsubscribeIcon } from './unsubscribe.svg';
const ResultPage = ({ courseTitle, error, intl }) => {
const errorDescription = (
<FormattedMessage
id="learning.goals.unsubscribe.errorDescription"
defaultMessage="We were unable to unsubscribe you from goal reminder emails. Please try again later or {contactSupport} for help."
values={{
contactSupport: (
<Hyperlink
className="text-reset"
style={{ textDecoration: 'underline' }}
destination={`${getConfig().CONTACT_URL}`}
>
{intl.formatMessage(messages.contactSupport)}
</Hyperlink>
),
}}
/>
);
const header = error
? intl.formatMessage(messages.errorHeader)
: intl.formatMessage(messages.header);
const description = error
? errorDescription
: intl.formatMessage(messages.description, { courseTitle });
return (
<>
<UnsubscribeIcon className="text-primary" alt="" />
<div role="heading" aria-level="1" className="h2">{header}</div>
<div className="row justify-content-center">
<div className="col-xl-7 col-12 p-0">{description}</div>
</div>
<Button variant="brand" href={`${getConfig().LMS_BASE_URL}/dashboard`} className="mt-4">
{intl.formatMessage(messages.goToDashboard)}
</Button>
</>
);
};
ResultPage.defaultProps = {
courseTitle: null,
error: false,
};
ResultPage.propTypes = {
courseTitle: PropTypes.string,
error: PropTypes.bool,
intl: intlShape.isRequired,
};
export default injectIntl(ResultPage);

View File

@@ -0,0 +1,3 @@
import GoalUnsubscribe from './GoalUnsubscribe';
export default GoalUnsubscribe;

View File

@@ -0,0 +1,36 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
contactSupport: {
id: 'learning.goals.unsubscribe.contact',
defaultMessage: 'contact support',
description: 'Its shown as a suggestion or recommendation for learner when their unsubscribing request has failed',
},
description: {
id: 'learning.goals.unsubscribe.description',
defaultMessage: 'You will no longer receive email reminders about your goal for {courseTitle}.',
description: 'It describes the consequences to learner when they unsubscribe of goal reminder service',
},
errorHeader: {
id: 'learning.goals.unsubscribe.errorHeader',
defaultMessage: 'Something went wrong',
description: 'It indicate that the unsubscribing request has failed',
},
goToDashboard: {
id: 'learning.goals.unsubscribe.goToDashboard',
defaultMessage: 'Go to dashboard',
description: 'Anchor text for button that redirects to dashboard page',
},
header: {
id: 'learning.goals.unsubscribe.header',
defaultMessage: 'Youve unsubscribed from goal reminders',
description: 'It indicate that the unsubscribing request was successful',
},
loading: {
id: 'learning.goals.unsubscribe.loading',
defaultMessage: 'Unsubscribing…',
description: 'Message shown when the unsubscribing request is processing',
},
});
export default messages;

View File

@@ -0,0 +1,5 @@
<svg width="167" height="153" viewBox="0 0 167 153" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M140.25 25.5H12.75V127.5H140.25V25.5ZM127.5 46L76.5 77.875L25.5 46V38.25L76.5 70.125L127.5 38.25V46Z" fill="currentColor"/>
<circle cx="134" cy="39" r="33" transform="rotate(-90 134 39)" fill="white"/>
<path d="M134 11C118.544 11 106 23.544 106 39C106 54.456 118.544 67 134 67C149.456 67 162 54.456 162 39C162 23.544 149.456 11 134 11ZM134 61.4C121.624 61.4 111.6 51.376 111.6 39C111.6 33.82 113.364 29.06 116.332 25.28L147.72 56.668C143.94 59.636 139.18 61.4 134 61.4ZM151.668 52.72L120.28 21.332C124.06 18.364 128.82 16.6 134 16.6C146.376 16.6 156.4 26.624 156.4 39C156.4 44.18 154.636 48.94 151.668 52.72Z" fill="#D23228"/>
</svg>

After

Width:  |  Height:  |  Size: 743 B

View File

@@ -0,0 +1,22 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
const LiveTab = () => {
const { courseId } = useSelector(state => state.courseHome);
const liveModel = useSelector(state => state.models.live);
useEffect(() => {
const iframe = document.getElementById('lti-tab-embed');
if (iframe) {
iframe.className += ' vh-100 w-100 border-0';
}
}, []);
return (
<div
id="live_tab"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: liveModel[courseId]?.iframe }}
/>
);
};
export default LiveTab;

Some files were not shown because too many files have changed in this diff Show More