diff --git a/package-lock.json b/package-lock.json
index a19bd2a761..94aad7ea5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-component-header": "^5.8.0",
- "@edx/frontend-lib-learning-assistant": "^2.6.0",
+ "@edx/frontend-lib-learning-assistant": "^2.9.0",
"@edx/frontend-lib-special-exams": "^3.1.3",
"@edx/frontend-platform": "^8.0.0",
"@edx/openedx-atlas": "^0.6.0",
@@ -2431,9 +2431,9 @@
}
},
"node_modules/@edx/frontend-lib-learning-assistant": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.6.0.tgz",
- "integrity": "sha512-73lggfdACTwpz6HA0VbjIetRxpWUMRyz4f79GIrmvHo2DvhICo84jVzbOSrwsvb43H0+52XjbHj6EeaBEDKHPA==",
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@edx/frontend-lib-learning-assistant/-/frontend-lib-learning-assistant-2.9.0.tgz",
+ "integrity": "sha512-Z85aRiMDv0N/QB8WTywsryJZ7GCtqwimxaW3knQU7dICn4UWRgqccHitoUtwZI6r6R6A57vn0T9Jpg5MZXJy+g==",
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@optimizely/react-sdk": "^2.9.2",
diff --git a/package.json b/package.json
index 3310dbdf7f..68eb695986 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-component-header": "^5.8.0",
- "@edx/frontend-lib-learning-assistant": "^2.6.0",
+ "@edx/frontend-lib-learning-assistant": "^2.9.0",
"@edx/frontend-lib-special-exams": "^3.1.3",
"@edx/frontend-platform": "^8.0.0",
"@edx/openedx-atlas": "^0.6.0",
diff --git a/src/constants.ts b/src/constants.ts
index 484d84530d..20f7d35d15 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -48,6 +48,13 @@ export const VERIFIED_MODES = [
'paid-bootcamp',
] as const satisfies readonly string[];
+export const AUDIT_MODES = [
+ 'audit',
+ 'honor',
+ 'unpaid-executive-education',
+ 'unpaid-bootcamp',
+] as const satisfies readonly string[];
+
export const WIDGETS = {
DISCUSSIONS: 'DISCUSSIONS',
NOTIFICATIONS: 'NOTIFICATIONS',
diff --git a/src/courseware/course/chat/Chat.jsx b/src/courseware/course/chat/Chat.jsx
index 4dbe81398c..702d76e784 100644
--- a/src/courseware/course/chat/Chat.jsx
+++ b/src/courseware/course/chat/Chat.jsx
@@ -3,9 +3,10 @@ import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { Xpert } from '@edx/frontend-lib-learning-assistant';
+import { getConfig } from '@edx/frontend-platform';
import { injectIntl } from '@edx/frontend-platform/i18n';
-import { VERIFIED_MODES } from '@src/constants';
+import { AUDIT_MODES, VERIFIED_MODES } from '@src/constants';
import { useModel } from '../../../generic/model-store';
const Chat = ({
@@ -21,28 +22,45 @@ const Chat = ({
} = useSelector(state => state.specialExams);
const course = useModel('coursewareMeta', courseId);
+ const {
+ accessExpiration,
+ start,
+ end,
+ } = course;
+
const hasVerifiedEnrollment = (
enrollmentMode !== null
&& enrollmentMode !== undefined
&& VERIFIED_MODES.includes(enrollmentMode)
);
+ // audit learners should only have access if the ENABLE_XPERT_AUDIT setting is true
+ const hasAuditEnrollmentAndAccess = (
+ enrollmentMode !== null
+ && enrollmentMode !== undefined
+ && AUDIT_MODES.includes(enrollmentMode)
+ && getConfig().ENABLE_XPERT_AUDIT
+ );
+
const validDates = () => {
const date = new Date();
const utcDate = date.toISOString();
- const startDate = course.start || utcDate;
- const endDate = course.end || utcDate;
+ const startDate = start || utcDate;
+ const endDate = end || utcDate;
+ const accessExpirationDate = accessExpiration && accessExpiration.expirationDate
+ ? accessExpiration.expirationDate : utcDate;
return (
startDate <= utcDate
&& utcDate <= endDate
+ && (hasAuditEnrollmentAndAccess ? utcDate <= accessExpirationDate : true)
);
};
const shouldDisplayChat = (
enabled
- && (hasVerifiedEnrollment || isStaff) // display only to verified learners or staff
+ && (hasVerifiedEnrollment || isStaff || hasAuditEnrollmentAndAccess)
&& validDates()
// it is necessary to check both whether the user is in an exam, and whether or not they are viewing an exam
// this will prevent the learner from interacting with the tool at any point of the exam flow, even at the
@@ -50,11 +68,18 @@ const Chat = ({
&& !(activeAttempt?.attempt_id || exam?.id)
);
+ const isUpgradeEligible = !hasVerifiedEnrollment && !isStaff;
+
return (
<>
{/* Use a portal to ensure that component overlay does not compete with learning MFE styles. */}
{shouldDisplayChat && (createPortal(
- ,
+ ,
document.body,
))}
>
diff --git a/src/courseware/course/chat/Chat.test.jsx b/src/courseware/course/chat/Chat.test.jsx
index 067565f34b..d50ead1adf 100644
--- a/src/courseware/course/chat/Chat.test.jsx
+++ b/src/courseware/course/chat/Chat.test.jsx
@@ -2,6 +2,8 @@ import { BrowserRouter } from 'react-router-dom';
import React from 'react';
import { Factory } from 'rosie';
+import { getConfig } from '@edx/frontend-platform';
+
import {
initializeMockApp,
initializeTestStore,
@@ -28,6 +30,10 @@ jest.mock('@edx/frontend-lib-learning-assistant', () => {
};
});
+jest.mock('@edx/frontend-platform', () => ({
+ getConfig: jest.fn().mockReturnValue({ ENABLE_XPERT_AUDIT: false }),
+}));
+
initializeMockApp();
const courseId = 'course-v1:edX+DemoX+Demo_Course';
@@ -225,4 +231,56 @@ describe('Chat', () => {
const chat = screen.queryByTestId(mockXpertTestId);
expect(chat).toBeInTheDocument();
});
+
+ it('displays component for audit learner if explicitly enabled', async () => {
+ getConfig.mockImplementation(() => ({ ENABLE_XPERT_AUDIT: true }));
+
+ store = await initializeTestStore({
+ courseMetadata: Factory.build('courseMetadata', {
+ access_expiration: { expiration_date: '' },
+ }),
+ });
+
+ render(
+
+
+ ,
+ { store },
+ );
+
+ const chat = screen.queryByTestId(mockXpertTestId);
+ expect(chat).toBeInTheDocument();
+ });
+
+ it('does not display component for audit learner if access deadline has passed', async () => {
+ getConfig.mockImplementation(() => ({ ENABLE_XPERT_AUDIT: true }));
+
+ store = await initializeTestStore({
+ courseMetadata: Factory.build('courseMetadata', {
+ access_expiration: { expiration_date: '2014-02-03T05:00:00Z' },
+ }),
+ });
+
+ render(
+
+
+ ,
+ { store },
+ );
+
+ const chat = screen.queryByTestId(mockXpertTestId);
+ expect(chat).not.toBeInTheDocument();
+ });
});
diff --git a/src/index.jsx b/src/index.jsx
index af7a153096..6da653de6c 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -175,6 +175,7 @@ initialize({
CHAT_RESPONSE_URL: process.env.CHAT_RESPONSE_URL || null,
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL || null,
SHOW_UNGRADED_ASSIGNMENT_PROGRESS: process.env.SHOW_UNGRADED_ASSIGNMENT_PROGRESS || false,
+ ENABLE_XPERT_AUDIT: process.env.ENABLE_XPERT_AUDIT || false,
}, 'LearnerAppConfig');
},
},