feat: update gating for chat component (#1550)
* feat: update gating for chat component * fix: add gating for access expiration * chore: upgrade learning assistant version
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
<Xpert courseId={courseId} contentToolsEnabled={contentToolsEnabled} unitId={unitId} />,
|
||||
<Xpert
|
||||
courseId={courseId}
|
||||
contentToolsEnabled={contentToolsEnabled}
|
||||
unitId={unitId}
|
||||
isUpgradeEligible={isUpgradeEligible}
|
||||
/>,
|
||||
document.body,
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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(
|
||||
<BrowserRouter>
|
||||
<Chat
|
||||
enrollmentMode="audit"
|
||||
isStaff={false}
|
||||
enabled
|
||||
courseId={courseId}
|
||||
contentToolsEnabled={false}
|
||||
/>
|
||||
</BrowserRouter>,
|
||||
{ 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(
|
||||
<BrowserRouter>
|
||||
<Chat
|
||||
enrollmentMode="audit"
|
||||
isStaff={false}
|
||||
enabled
|
||||
courseId={courseId}
|
||||
contentToolsEnabled={false}
|
||||
/>
|
||||
</BrowserRouter>,
|
||||
{ store },
|
||||
);
|
||||
|
||||
const chat = screen.queryByTestId(mockXpertTestId);
|
||||
expect(chat).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user