feat: fetch exam access token (#1083)

* feat: fetch exam access token

* build: update frontend lib special exams version
This commit is contained in:
Varsha
2023-04-17 14:59:05 -04:00
committed by GitHub
parent 764befd4bd
commit 01f9d8f50b
4 changed files with 78 additions and 11 deletions

14
package-lock.json generated
View File

@@ -12,7 +12,7 @@
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "11.6.3",
"@edx/frontend-component-header": "3.6.4",
"@edx/frontend-lib-special-exams": "2.10.0",
"@edx/frontend-lib-special-exams": "2.12.0",
"@edx/frontend-platform": "3.4.1",
"@edx/paragon": "20.28.4",
"@fortawesome/fontawesome-svg-core": "1.3.0",
@@ -3272,9 +3272,9 @@
}
},
"node_modules/@edx/frontend-lib-special-exams": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.10.0.tgz",
"integrity": "sha512-YHTYlmHIVM6KRTklbM3ERHmu1adRoMIpYTWwn/KhEQ/a6YQfRM8SJt8uawj4ceSRftJ7E9QWMGy/21KfnIYOxg==",
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.12.0.tgz",
"integrity": "sha512-VltUW9bZ+Ha9Gw4xplEJjfZi4zjUJJWsAFOVeX8n+ZJLko4qDU46tL7EOLAJyMaiAtjvHent8MNIfcGBbqgcFQ==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.11.2",
@@ -28482,9 +28482,9 @@
}
},
"@edx/frontend-lib-special-exams": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.10.0.tgz",
"integrity": "sha512-YHTYlmHIVM6KRTklbM3ERHmu1adRoMIpYTWwn/KhEQ/a6YQfRM8SJt8uawj4ceSRftJ7E9QWMGy/21KfnIYOxg==",
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-2.12.0.tgz",
"integrity": "sha512-VltUW9bZ+Ha9Gw4xplEJjfZi4zjUJJWsAFOVeX8n+ZJLko4qDU46tL7EOLAJyMaiAtjvHent8MNIfcGBbqgcFQ==",
"requires": {
"@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.11.2",

View File

@@ -32,7 +32,7 @@
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "11.6.3",
"@edx/frontend-component-header": "3.6.4",
"@edx/frontend-lib-special-exams": "2.10.0",
"@edx/frontend-lib-special-exams": "2.12.0",
"@edx/frontend-platform": "3.4.1",
"@edx/paragon": "20.28.4",
"@fortawesome/fontawesome-svg-core": "1.3.0",

View File

@@ -1,6 +1,8 @@
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import { AppContext, ErrorPage } from '@edx/frontend-platform/react';
import { getExamAccess, fetchExamAccess, isExam } from '@edx/frontend-lib-special-exams';
import { Modal } from '@edx/paragon';
import PropTypes from 'prop-types';
import React, {
@@ -65,6 +67,16 @@ function useLoadBearingHook(id) {
}, [id]);
}
function addExamAccessToIframeUrl(accessToken, iframeUrl) {
let url = iframeUrl;
if (isExam()) {
if (accessToken) {
url += `&exam_access=${accessToken}`;
}
}
return url;
}
export function sendUrlHashToFrame(frame) {
const { hash } = window.location;
if (hash) {
@@ -83,6 +95,7 @@ const Unit = ({
}) => {
const { authenticatedUser } = useContext(AppContext);
const view = authenticatedUser ? 'student_view' : 'public_view';
let iframeUrl = `${getConfig().LMS_BASE_URL}/xblock/${id}?show_title=0&show_bookmark_button=0&recheck_access=1&view=${view}`;
if (format) {
iframeUrl += `&format=${format}`;
@@ -93,6 +106,8 @@ const Unit = ({
const [showError, setShowError] = useState(false);
const [modalOptions, setModalOptions] = useState({ open: false });
const [shouldDisplayHonorCode, setShouldDisplayHonorCode] = useState(false);
const [examAccessToken, setExamAccessToken] = useState('');
const [blockExamAccess, setBlockExamAccess] = useState(isExam());
const unit = useModel('units', id);
const course = useModel('coursewareMeta', courseId);
@@ -140,6 +155,16 @@ const Unit = ({
sendUrlHashToFrame(document.getElementById('unit-iframe'));
}, [id, setIframeHeight, hasLoaded, iframeHeight, setHasLoaded, onLoaded]);
useEffect(() => {
if (isExam()) {
fetchExamAccess().finally(() => {
const examAccess = getExamAccess();
setExamAccessToken(examAccess);
setBlockExamAccess(false);
}).catch((error) => logError(error));
}
}, [id]);
return (
<div className="unit">
<h1 className="mb-0 h3">{unit.title}</h1>
@@ -175,7 +200,7 @@ const Unit = ({
<HonorCode courseId={courseId} />
</Suspense>
)}
{!shouldDisplayHonorCode && !hasLoaded && !showError && (
{(!shouldDisplayHonorCode || blockExamAccess) && !hasLoaded && !showError && (
<PageLoading
srMessage={intl.formatMessage(messages.loadingSequence)}
/>
@@ -208,12 +233,12 @@ const Unit = ({
dialogClassName="modal-lti"
/>
)}
{!shouldDisplayHonorCode && (
{!shouldDisplayHonorCode && !blockExamAccess && (
<div className="unit-iframe-wrapper">
<iframe
id="unit-iframe"
title={unit.title}
src={iframeUrl}
src={addExamAccessToIframeUrl(examAccessToken, iframeUrl)}
allow={IFRAME_FEATURE_POLICY}
allowFullScreen
height={iframeHeight}

View File

@@ -1,10 +1,25 @@
import React from 'react';
import { Factory } from 'rosie';
import { fetchExamAccess, getExamAccess, isExam } from '@edx/frontend-lib-special-exams';
import {
initializeTestStore, loadUnit, messageEvent, render, screen, waitFor,
} from '../../../setupTest';
import Unit, { sendUrlHashToFrame } from './Unit';
const originalIsExam = jest.requireActual('@edx/frontend-lib-special-exams').isExam();
const originalFetchExamAccess = jest.requireActual('@edx/frontend-lib-special-exams').fetchExamAccess();
const originalGetExamAccess = jest.requireActual('@edx/frontend-lib-special-exams').getExamAccess();
jest.mock('@edx/frontend-lib-special-exams', () => ({
...jest.requireActual('@edx/frontend-lib-special-exams'),
isExam: jest.fn(),
fetchExamAccess: jest.fn(),
getExamAccess: jest.fn(),
}));
isExam.mockImplementation(() => originalIsExam).mockReturnValue(false);
fetchExamAccess.mockImplementation(() => originalFetchExamAccess).mockResolvedValue();
const examAccessToken = 'EXAMACCESSTOKEN';
getExamAccess.mockImplementation(() => originalGetExamAccess).mockReturnValue(examAccessToken);
describe('Unit', () => {
let mockData;
const courseMetadata = Factory.build(
@@ -174,4 +189,31 @@ describe('Unit', () => {
expect(React.useEffect).toHaveBeenCalled();
expect(mockHashCheck).toHaveBeenCalled();
});
it('updates url if exam and exam access granted', async () => {
isExam.mockReturnValue(true);
render(<Unit {...mockData} />);
expect(isExam).toHaveBeenCalled();
await waitFor(() => expect(screen.getByTitle(unit.display_name)).toHaveAttribute('src', `http://localhost:18000/xblock/${mockData.id}?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=${mockData.format}&exam_access=${examAccessToken}`));
});
it('does not update url if exam and exam access not granted', async () => {
isExam.mockReturnValue(true);
fetchExamAccess.mockRejectedValue();
getExamAccess.mockReturnValue('');
render(<Unit {...mockData} />);
expect(isExam).toHaveBeenCalled();
await waitFor(() => expect(screen.getByTitle(unit.display_name)).toHaveAttribute('src', `http://localhost:18000/xblock/${mockData.id}?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=${mockData.format}`));
});
it('does not update url if not exam', async () => {
isExam.mockReturnValue(false);
render(<Unit {...mockData} />);
expect(isExam).toHaveBeenCalled();
const renderedUnit = screen.getByTitle(unit.display_name);
expect(renderedUnit).toHaveAttribute('src', `http://localhost:18000/xblock/${mockData.id}?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=${mockData.format}`);
});
});