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:
Alison Langston
2024-12-09 16:21:40 -05:00
committed by GitHub
parent cd56ffaf9d
commit dafdcad2b4
6 changed files with 101 additions and 10 deletions

8
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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',

View File

@@ -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,
))}
</>

View File

@@ -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();
});
});

View File

@@ -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');
},
},