diff --git a/.env b/.env
index 912f29cb..0dc298da 100644
--- a/.env
+++ b/.env
@@ -2,14 +2,19 @@
# If you add a new learning MFE-specific variable, please note it there!
NODE_ENV='production'
+
ACCESS_TOKEN_COOKIE_NAME=''
BASE_URL=''
CONTACT_URL=''
CREDENTIALS_BASE_URL=''
+CREDIT_HELP_LINK_URL=''
CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL=''
ECOMMERCE_BASE_URL=''
+ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
+FAVICON_URL=''
IGNORED_ERROR_REGEX=''
INSIGHTS_BASE_URL=''
LANGUAGE_PREFERENCE_COOKIE_NAME=''
@@ -19,12 +24,12 @@ LOGOUT_URL=''
LOGO_URL=''
LOGO_TRADEMARK_URL=''
LOGO_WHITE_URL=''
-FAVICON_URL=''
MARKETING_SITE_BASE_URL=''
ORDER_HISTORY_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT=''
SEARCH_CATALOG_URL=''
SEGMENT_KEY=''
+SESSION_COOKIE_DOMAIN=''
SITE_NAME=''
SOCIAL_UTM_MILESTONE_CAMPAIGN=''
STUDIO_BASE_URL=''
@@ -36,6 +41,3 @@ TERMS_OF_SERVICE_URL=''
TWITTER_HASHTAG=''
TWITTER_URL=''
USER_INFO_COOKIE_NAME=''
-SESSION_COOKIE_DOMAIN=''
-ENABLE_JUMPNAV='true'
-ENABLE_NOTICES=''
diff --git a/.env.development b/.env.development
index 16d6764d..ec78360c 100644
--- a/.env.development
+++ b/.env.development
@@ -2,14 +2,19 @@
# If you add a new learning MFE-specific variable, please note it there!
NODE_ENV='development'
+
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
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'
ECOMMERCE_BASE_URL='http://localhost:18130'
+ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
+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'
@@ -18,7 +23,6 @@ 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
-FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000
@@ -37,5 +41,3 @@ TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
SESSION_COOKIE_DOMAIN='localhost'
-ENABLE_JUMPNAV='true'
-ENABLE_NOTICES=''
diff --git a/.env.test b/.env.test
index 24a5c93e..699950a8 100644
--- a/.env.test
+++ b/.env.test
@@ -2,14 +2,19 @@
# 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='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'
ECOMMERCE_BASE_URL='http://localhost:18130'
+ENABLE_JUMPNAV='true'
+ENABLE_NOTICES=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
+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'
@@ -18,7 +23,6 @@ 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
-FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
MARKETING_SITE_BASE_URL='http://localhost:18000'
ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000
@@ -36,5 +40,3 @@ 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'
-ENABLE_JUMPNAV='true'
-ENABLE_NOTICES=''
diff --git a/README.rst b/README.rst
index 4af4a220..7f869dfc 100644
--- a/README.rst
+++ b/README.rst
@@ -71,6 +71,15 @@ as documented in the Open edX Developer Guide under
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.
@@ -110,8 +119,3 @@ TWITTER_URL
Example: https://twitter.com/edXOnline
-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
diff --git a/src/course-home/data/__factories__/progressTabData.factory.js b/src/course-home/data/__factories__/progressTabData.factory.js
index aa555edb..1ff83241 100644
--- a/src/course-home/data/__factories__/progressTabData.factory.js
+++ b/src/course-home/data/__factories__/progressTabData.factory.js
@@ -17,6 +17,7 @@ Factory.define('progressTabData')
percent: 1,
is_passing: true,
},
+ credit_course_requirements: null,
section_scores: [
{
display_name: 'First section',
diff --git a/src/course-home/data/__snapshots__/redux.test.js.snap b/src/course-home/data/__snapshots__/redux.test.js.snap
index 1f119f21..17c85ee7 100644
--- a/src/course-home/data/__snapshots__/redux.test.js.snap
+++ b/src/course-home/data/__snapshots__/redux.test.js.snap
@@ -581,6 +581,7 @@ Object {
"visiblePercent": 1,
},
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
+ "creditCourseRequirements": null,
"end": "3027-03-31T00:00:00Z",
"enrollmentMode": "audit",
"gradesFeatureIsFullyLocked": false,
diff --git a/src/course-home/progress-tab/ProgressTab.test.jsx b/src/course-home/progress-tab/ProgressTab.test.jsx
index 9bf74321..2bacafc3 100644
--- a/src/course-home/progress-tab/ProgressTab.test.jsx
+++ b/src/course-home/progress-tab/ProgressTab.test.jsx
@@ -487,6 +487,29 @@ describe('Progress Tab', () => {
// visible to them, which is non-passing
expect(screen.getByText('A weighted grade of 75% is required to pass in this course')).toBeInTheDocument();
});
+
+ it('renders correct title when credit information is available', async () => {
+ setTabData({
+ credit_course_requirements: {
+ eligibility_status: 'eligible',
+ requirements: [
+ {
+ namespace: 'proctored_exam',
+ name: 'i4x://edX/DemoX/proctoring-block/final_uuid',
+ display_name: 'Proctored Mid Term Exam',
+ criteria: {},
+ reason: {},
+ status: 'satisfied',
+ status_date: '2015-06-26 11:07:42',
+ order: 1,
+ },
+ ],
+ },
+ });
+
+ await fetchAndRender();
+ expect(screen.getByText('Grades & Credit')).toBeInTheDocument();
+ });
});
describe('Grade Summary', () => {
@@ -1189,6 +1212,54 @@ describe('Progress Tab', () => {
});
});
+ describe('Credit Information', () => {
+ it('renders credit information when provided', async () => {
+ setTabData({
+ credit_course_requirements: {
+ eligibility_status: 'eligible',
+ requirements: [
+ {
+ namespace: 'proctored_exam',
+ name: 'i4x://edX/DemoX/proctoring-block/final_uuid',
+ display_name: 'Proctored Mid Term Exam',
+ criteria: {},
+ reason: {},
+ status: null,
+ status_date: '2015-06-26 11:07:42',
+ order: 1,
+ },
+ {
+ namespace: 'grade',
+ name: 'i4x://edX/DemoX/proctoring-block/final_uuid',
+ display_name: 'Minimum Passing Grade',
+ criteria: { min_grade: 0.8 },
+ reason: { final_grade: 0.95 },
+ status: 'satisfied',
+ status_date: '2015-06-26 11:07:44',
+ order: 2,
+ },
+ ],
+ },
+ });
+
+ await fetchAndRender();
+ expect(screen.getByText('Grades & Credit')).toBeInTheDocument();
+ expect(screen.getByText('Requirements for course credit')).toBeInTheDocument();
+ expect(screen.getByText('You have met the requirements for credit in this course.', { exact: false })).toBeInTheDocument();
+ expect(screen.getByText('Proctored Mid Term Exam:')).toBeInTheDocument();
+ // 80% comes from the criteria.minGrade being 0.8
+ expect(screen.getByText('Minimum grade for credit (80%):')).toBeInTheDocument();
+ // Completed because the grade requirement has been satisfied
+ expect(screen.getByText('Completed')).toBeInTheDocument();
+ });
+
+ it('does not render credit information when it is not provided', async () => {
+ await fetchAndRender();
+ expect(screen.queryByText('Grades & Credit')).not.toBeInTheDocument();
+ expect(screen.queryByText('Requirements for course credit.')).not.toBeInTheDocument();
+ });
+ });
+
describe('Access expiration masquerade banner', () => {
it('renders banner when masquerading as a user', async () => {
setMetadata({ is_enrolled: true, original_user_is_staff: true });
diff --git a/src/course-home/progress-tab/credit-information/CreditInformation.jsx b/src/course-home/progress-tab/credit-information/CreditInformation.jsx
new file mode 100644
index 00000000..673b18c8
--- /dev/null
+++ b/src/course-home/progress-tab/credit-information/CreditInformation.jsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { getConfig } from '@edx/frontend-platform';
+import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { CheckCircle, WarningFilled, WatchFilled } from '@edx/paragon/icons';
+import { Hyperlink, Icon } from '@edx/paragon';
+
+import { useModel } from '../../../generic/model-store';
+import { DashboardLink } from '../../../shared/links';
+
+import messages from './messages';
+
+function CreditInformation({ intl }) {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ creditCourseRequirements,
+ } = useModel('progress', courseId);
+
+ if (!creditCourseRequirements) { return null; }
+
+ let eligibilityStatus;
+ let requirementStatus;
+ const requirements = [];
+ const dashboardLink =
+ {requirement.namespace === 'grade' + ? `${intl.formatMessage(messages.minimumGrade, { minGrade: Number(requirement.criteria.minGrade) * 100 })}:` + : `${requirement.displayName}:`} +
+{eligibilityStatus}
+ {requirements} + > + ); +} + +CreditInformation.propTypes = { + intl: intlShape.isRequired, +}; + +export default injectIntl(CreditInformation); diff --git a/src/course-home/progress-tab/credit-information/messages.js b/src/course-home/progress-tab/credit-information/messages.js new file mode 100644 index 00000000..c80b2526 --- /dev/null +++ b/src/course-home/progress-tab/credit-information/messages.js @@ -0,0 +1,34 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + completed: { + id: 'progress.creditInformation.completed', + defaultMessage: 'Completed', + }, + courseCredit: { + id: 'progress.creditInformation.courseCredit', + defaultMessage: 'course credit', + }, + minimumGrade: { + id: 'progress.creditInformation.minimumGrade', + defaultMessage: 'Minimum grade for credit ({minGrade}%)', + }, + requirementsHeader: { + id: 'progress.creditInformation.requirementsHeader', + defaultMessage: 'Requirements for course credit', + }, + upcoming: { + id: 'progress.creditInformation.upcoming', + defaultMessage: 'Upcoming', + }, + verificationFailed: { + id: 'progress.creditInformation.verificationFailed', + defaultMessage: 'Verification failed', + }, + verificationSubmitted: { + id: 'progress.creditInformation.verificationSubmitted', + defaultMessage: 'Verification submitted', + }, +}); + +export default messages; diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx index c350296a..410ddfa0 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx @@ -7,6 +7,7 @@ import { useModel } from '../../../../generic/model-store'; import CourseGradeFooter from './CourseGradeFooter'; import CourseGradeHeader from './CourseGradeHeader'; import GradeBar from './GradeBar'; +import CreditInformation from '../../credit-information/CreditInformation'; import messages from '../messages'; @@ -16,6 +17,7 @@ function CourseGrade({ intl }) { } = useSelector(state => state.courseHome); const { + creditCourseRequirements, gradesFeatureIsFullyLocked, gradesFeatureIsPartiallyLocked, gradingPolicy: { @@ -32,14 +34,20 @@ function CourseGrade({ intl }) { {(gradesFeatureIsFullyLocked || gradesFeatureIsPartiallyLocked) &&{intl.formatMessage(messages.courseGradeBody)}