Skip to content

Commit

Permalink
feat: install Xpert chatbot from frontend-lib-learning-assistant (#1166)
Browse files Browse the repository at this point in the history
This commit installs the Xpert chatbot feature from the frontend-lib-learning-assistant repository into the frontend-lib-learning application.

This component is rendered by the Course component. The component is only rendered when a few conditions are satisfied.
  • Loading branch information
MichaelRoytman committed Aug 23, 2023
1 parent 2b9b3db commit 2d29827
Show file tree
Hide file tree
Showing 18 changed files with 338 additions and 260 deletions.
84 changes: 84 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@edx/frontend-component-footer": "12.0.0",
"@edx/frontend-component-header": "4.0.0",
"@edx/frontend-lib-special-exams": "2.20.1",
"@edx/frontend-lib-learning-assistant": "^1.4.0",
"@edx/frontend-platform": "4.3.0",
"@edx/paragon": "20.46.0",
"@edx/react-unit-test-utils": "npm:@edx/react-unit-test-utils@1.5.3",
Expand Down
9 changes: 9 additions & 0 deletions src/course-home/data/__snapshots__/redux.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Object {
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
Expand Down Expand Up @@ -336,6 +339,9 @@ Object {
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
Expand Down Expand Up @@ -532,6 +538,9 @@ Object {
"sequenceMightBeUnit": false,
"sequenceStatus": "loading",
},
"learningAssistant": ObjectContaining {
"conversationId": Any<String>,
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course": Object {
Expand Down
27 changes: 24 additions & 3 deletions src/course-home/data/redux.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,14 @@ describe('Data layer integration tests', () => {

const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This causes snapshots to fail, because this UUID
// is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({
conversationId: expect.any(String),
}),
});
});

it.each([401, 403, 404])(
Expand Down Expand Up @@ -111,7 +118,14 @@ describe('Data layer integration tests', () => {

const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This causes snapshots to fail, because this UUID
// is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({
conversationId: expect.any(String),
}),
});
});

it.each([401, 403, 404])(
Expand Down Expand Up @@ -156,7 +170,14 @@ describe('Data layer integration tests', () => {

const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
expect(state).toMatchSnapshot({
// The Xpert chatbot (frontend-lib-learning-assistant) generates a unique UUID
// to keep track of conversations. This causes snapshots to fail, because this UUID
// is generated on each run of the snapshot. Instead, we use an asymmetric matcher here.
learningAssistant: expect.objectContaining({
conversationId: expect.any(String),
}),
});
});

it('Should handle the url including a targetUserId', async () => {
Expand Down
6 changes: 3 additions & 3 deletions src/courseware/course/Course.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import { AlertList } from '../../generic/user-messages';
import Sequence from './sequence';

import { CelebrationModal, shouldCelebrateOnSectionLoad, WeeklyGoalCelebrationModal } from './celebration';
import Chat from './chat/Chat';
import ContentTools from './content-tools';
import CourseBreadcrumbs from './CourseBreadcrumbs';
import SidebarProvider from './sidebar/SidebarContextProvider';
import SidebarTriggers from './sidebar/SidebarTriggers';
import ChatTrigger from './lti-modal/ChatTrigger';

import { useModel } from '../../generic/model-store';
import { getSessionStorage, setSessionStorage } from '../../data/sessionStorage';
Expand Down Expand Up @@ -93,10 +93,10 @@ const Course = ({
/>
{shouldDisplayTriggers && (
<>
<ChatTrigger
<Chat
enabled={course.learningAssistantEnabled}
enrollmentMode={course.enrollmentMode}
isStaff={isStaff}
launchUrl={course.learningAssistantLaunchUrl}
courseId={courseId}
/>
<SidebarTriggers />
Expand Down
55 changes: 55 additions & 0 deletions src/courseware/course/chat/Chat.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';

import { Xpert } from '@edx/frontend-lib-learning-assistant';
import { injectIntl } from '@edx/frontend-platform/i18n';

const Chat = ({
enabled,
enrollmentMode,
isStaff,
courseId,
}) => {
const VERIFIED_MODES = [
'professional',
'verified',
'no-id-professional',
'credit',
'masters',
'executive-education',
];

const isVerifiedEnrollmentMode = (
enrollmentMode !== null
&& enrollmentMode !== undefined
&& VERIFIED_MODES.some(mode => mode === enrollmentMode)
);

const shouldDisplayChat = (
enabled
&& (isVerifiedEnrollmentMode || isStaff) // display only to non-audit or staff
);

return (
<>
{/* Use a portal to ensure that component overlay does not compete with learning MFE styles. */}
{shouldDisplayChat && (createPortal(
<Xpert courseId={courseId} />,
document.body,
))}
</>
);
};

Chat.propTypes = {
isStaff: PropTypes.bool.isRequired,
enabled: PropTypes.bool.isRequired,
enrollmentMode: PropTypes.string,
courseId: PropTypes.string.isRequired,
};

Chat.defaultProps = {
enrollmentMode: null,
};

export default injectIntl(Chat);

0 comments on commit 2d29827

Please sign in to comment.