Skip to content

Commit dafdcad

Browse files
authored
feat: update gating for chat component (#1550)
* feat: update gating for chat component * fix: add gating for access expiration * chore: upgrade learning assistant version
1 parent cd56ffa commit dafdcad

File tree

6 files changed

+101
-10
lines changed

6 files changed

+101
-10
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
3737
"@edx/browserslist-config": "1.2.0",
3838
"@edx/frontend-component-header": "^5.8.0",
39-
"@edx/frontend-lib-learning-assistant": "^2.6.0",
39+
"@edx/frontend-lib-learning-assistant": "^2.9.0",
4040
"@edx/frontend-lib-special-exams": "^3.1.3",
4141
"@edx/frontend-platform": "^8.0.0",
4242
"@edx/openedx-atlas": "^0.6.0",

src/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export const VERIFIED_MODES = [
4848
'paid-bootcamp',
4949
] as const satisfies readonly string[];
5050

51+
export const AUDIT_MODES = [
52+
'audit',
53+
'honor',
54+
'unpaid-executive-education',
55+
'unpaid-bootcamp',
56+
] as const satisfies readonly string[];
57+
5158
export const WIDGETS = {
5259
DISCUSSIONS: 'DISCUSSIONS',
5360
NOTIFICATIONS: 'NOTIFICATIONS',

src/courseware/course/chat/Chat.jsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { useSelector } from 'react-redux';
33
import PropTypes from 'prop-types';
44

55
import { Xpert } from '@edx/frontend-lib-learning-assistant';
6+
import { getConfig } from '@edx/frontend-platform';
67
import { injectIntl } from '@edx/frontend-platform/i18n';
78

8-
import { VERIFIED_MODES } from '@src/constants';
9+
import { AUDIT_MODES, VERIFIED_MODES } from '@src/constants';
910
import { useModel } from '../../../generic/model-store';
1011

1112
const Chat = ({
@@ -21,40 +22,64 @@ const Chat = ({
2122
} = useSelector(state => state.specialExams);
2223
const course = useModel('coursewareMeta', courseId);
2324

25+
const {
26+
accessExpiration,
27+
start,
28+
end,
29+
} = course;
30+
2431
const hasVerifiedEnrollment = (
2532
enrollmentMode !== null
2633
&& enrollmentMode !== undefined
2734
&& VERIFIED_MODES.includes(enrollmentMode)
2835
);
2936

37+
// audit learners should only have access if the ENABLE_XPERT_AUDIT setting is true
38+
const hasAuditEnrollmentAndAccess = (
39+
enrollmentMode !== null
40+
&& enrollmentMode !== undefined
41+
&& AUDIT_MODES.includes(enrollmentMode)
42+
&& getConfig().ENABLE_XPERT_AUDIT
43+
);
44+
3045
const validDates = () => {
3146
const date = new Date();
3247
const utcDate = date.toISOString();
3348

34-
const startDate = course.start || utcDate;
35-
const endDate = course.end || utcDate;
49+
const startDate = start || utcDate;
50+
const endDate = end || utcDate;
51+
const accessExpirationDate = accessExpiration && accessExpiration.expirationDate
52+
? accessExpiration.expirationDate : utcDate;
3653

3754
return (
3855
startDate <= utcDate
3956
&& utcDate <= endDate
57+
&& (hasAuditEnrollmentAndAccess ? utcDate <= accessExpirationDate : true)
4058
);
4159
};
4260

4361
const shouldDisplayChat = (
4462
enabled
45-
&& (hasVerifiedEnrollment || isStaff) // display only to verified learners or staff
63+
&& (hasVerifiedEnrollment || isStaff || hasAuditEnrollmentAndAccess)
4664
&& validDates()
4765
// it is necessary to check both whether the user is in an exam, and whether or not they are viewing an exam
4866
// this will prevent the learner from interacting with the tool at any point of the exam flow, even at the
4967
// entrance interstitial.
5068
&& !(activeAttempt?.attempt_id || exam?.id)
5169
);
5270

71+
const isUpgradeEligible = !hasVerifiedEnrollment && !isStaff;
72+
5373
return (
5474
<>
5575
{/* Use a portal to ensure that component overlay does not compete with learning MFE styles. */}
5676
{shouldDisplayChat && (createPortal(
57-
<Xpert courseId={courseId} contentToolsEnabled={contentToolsEnabled} unitId={unitId} />,
77+
<Xpert
78+
courseId={courseId}
79+
contentToolsEnabled={contentToolsEnabled}
80+
unitId={unitId}
81+
isUpgradeEligible={isUpgradeEligible}
82+
/>,
5883
document.body,
5984
))}
6085
</>

src/courseware/course/chat/Chat.test.jsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { BrowserRouter } from 'react-router-dom';
22
import React from 'react';
33
import { Factory } from 'rosie';
44

5+
import { getConfig } from '@edx/frontend-platform';
6+
57
import {
68
initializeMockApp,
79
initializeTestStore,
@@ -28,6 +30,10 @@ jest.mock('@edx/frontend-lib-learning-assistant', () => {
2830
};
2931
});
3032

33+
jest.mock('@edx/frontend-platform', () => ({
34+
getConfig: jest.fn().mockReturnValue({ ENABLE_XPERT_AUDIT: false }),
35+
}));
36+
3137
initializeMockApp();
3238

3339
const courseId = 'course-v1:edX+DemoX+Demo_Course';
@@ -225,4 +231,56 @@ describe('Chat', () => {
225231
const chat = screen.queryByTestId(mockXpertTestId);
226232
expect(chat).toBeInTheDocument();
227233
});
234+
235+
it('displays component for audit learner if explicitly enabled', async () => {
236+
getConfig.mockImplementation(() => ({ ENABLE_XPERT_AUDIT: true }));
237+
238+
store = await initializeTestStore({
239+
courseMetadata: Factory.build('courseMetadata', {
240+
access_expiration: { expiration_date: '' },
241+
}),
242+
});
243+
244+
render(
245+
<BrowserRouter>
246+
<Chat
247+
enrollmentMode="audit"
248+
isStaff={false}
249+
enabled
250+
courseId={courseId}
251+
contentToolsEnabled={false}
252+
/>
253+
</BrowserRouter>,
254+
{ store },
255+
);
256+
257+
const chat = screen.queryByTestId(mockXpertTestId);
258+
expect(chat).toBeInTheDocument();
259+
});
260+
261+
it('does not display component for audit learner if access deadline has passed', async () => {
262+
getConfig.mockImplementation(() => ({ ENABLE_XPERT_AUDIT: true }));
263+
264+
store = await initializeTestStore({
265+
courseMetadata: Factory.build('courseMetadata', {
266+
access_expiration: { expiration_date: '2014-02-03T05:00:00Z' },
267+
}),
268+
});
269+
270+
render(
271+
<BrowserRouter>
272+
<Chat
273+
enrollmentMode="audit"
274+
isStaff={false}
275+
enabled
276+
courseId={courseId}
277+
contentToolsEnabled={false}
278+
/>
279+
</BrowserRouter>,
280+
{ store },
281+
);
282+
283+
const chat = screen.queryByTestId(mockXpertTestId);
284+
expect(chat).not.toBeInTheDocument();
285+
});
228286
});

src/index.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ initialize({
175175
CHAT_RESPONSE_URL: process.env.CHAT_RESPONSE_URL || null,
176176
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL || null,
177177
SHOW_UNGRADED_ASSIGNMENT_PROGRESS: process.env.SHOW_UNGRADED_ASSIGNMENT_PROGRESS || false,
178+
ENABLE_XPERT_AUDIT: process.env.ENABLE_XPERT_AUDIT || false,
178179
}, 'LearnerAppConfig');
179180
},
180181
},

0 commit comments

Comments
 (0)