added component for proctoring panel (#345)

added 404 test

updated for feedback

updated test
This commit is contained in:
alangsto
2021-01-21 12:15:56 -05:00
committed by GitHub
parent 96ef87886f
commit 68c8d31dd1
7 changed files with 233 additions and 2 deletions

View File

@@ -130,6 +130,20 @@ export async function getProgressTabData(courseId) {
}
}
export async function getProctoringInfoData(courseId) {
const url = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?course_id=${courseId}`;
try {
const { data } = await getAuthenticatedHttpClient().get(url);
return data;
} catch (error) {
const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) {
return {};
}
throw error;
}
}
export async function getOutlineTabData(courseId) {
const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/outline/${courseId}`;
let { tabData } = {};

View File

@@ -25,6 +25,7 @@ import useOfferAlert from '../../alerts/offer-alert';
import usePrivateCourseAlert from '../../alerts/private-course-alert';
import { useModel } from '../../generic/model-store';
import WelcomeMessage from './widgets/WelcomeMessage';
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
function OutlineTab({ intl }) {
const {
@@ -162,6 +163,9 @@ function OutlineTab({ intl }) {
</div>
{rootCourseId && (
<div className="col col-12 col-md-4">
<ProctoringInfoPanel
courseId={courseId}
/>
{courseGoalToDisplay && goalOptions.length > 0 && (
<UpdateGoalSelector
courseId={courseId}

View File

@@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event';
import buildSimpleCourseBlocks from '../data/__factories__/courseBlocks.factory';
import {
fireEvent, initializeMockApp, logUnhandledRequests, render, screen, waitFor,
fireEvent, initializeMockApp, logUnhandledRequests, render, screen, waitFor, act,
} from '../../setupTest';
import executeThunk from '../../utils';
import * as thunks from '../data/thunks';
@@ -25,6 +25,7 @@ describe('Outline Tab', () => {
const enrollmentUrl = `${getConfig().LMS_BASE_URL}/api/enrollment/v1/enrollment`;
const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/save_course_goal`;
const outlineUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/outline/${courseId}`;
const proctoringInfoUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/user_onboarding/status?course_id=${courseId}`;
const store = initializeStore();
const defaultMetadata = Factory.build('courseHomeMetadata', { courseId });
@@ -42,7 +43,7 @@ describe('Outline Tab', () => {
async function fetchAndRender() {
await executeThunk(thunks.fetchOutlineTab(courseId), store.dispatch);
render(<OutlineTab />, { store });
await act(async () => render(<OutlineTab />, { store }));
}
beforeEach(async () => {
@@ -53,6 +54,7 @@ describe('Outline Tab', () => {
axiosMock.onPost(enrollmentUrl).reply(200, {});
axiosMock.onPost(goalUrl).reply(200, { header: 'Success' });
axiosMock.onGet(outlineUrl).reply(200, defaultTabData);
axiosMock.onGet(proctoringInfoUrl).reply(200, { onboarding_status: 'created', onboarding_link: 'test' });
logUnhandledRequests(axiosMock);
});
@@ -473,4 +475,33 @@ describe('Outline Tab', () => {
});
});
});
describe('Proctoring Info Panel', () => {
it('appears', async () => {
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).toBeInTheDocument();
});
it('appears for verified', async () => {
axiosMock.onGet(proctoringInfoUrl).reply(200, { onboarding_status: 'verified', onboarding_link: 'test' });
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
expect(screen.queryByRole('link', { name: 'Complete Onboarding' })).not.toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).toBeInTheDocument();
});
it('appears for rejected', async () => {
axiosMock.onGet(proctoringInfoUrl).reply(200, { onboarding_status: 'rejected', onboarding_link: 'test' });
await fetchAndRender();
await screen.findByText('This course contains proctored exams');
expect(screen.queryByRole('link', { name: 'Complete Onboarding' })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).toBeInTheDocument();
});
it('does not appear for 404', async () => {
axiosMock.onGet(proctoringInfoUrl).reply(404);
expect(screen.queryByRole('link', { name: 'Review instructions and system requirements' })).not.toBeInTheDocument();
});
});
});

View File

@@ -112,6 +112,78 @@ const messages = defineMessages({
defaultMessage: 'Welcome to',
description: 'This precedes the title of the course',
},
proctoringInfoPanel: {
id: 'learning.proctoringPanel.header',
defaultMessage: 'This course contains proctored exams',
},
notStartedProctoringStatus: {
id: 'learning.proctoringPanel.status.notStarted',
defaultMessage: 'Not Started',
},
startedProctoringStatus: {
id: 'learning.proctoringPanel.status.started',
defaultMessage: 'Started',
},
submittedProctoringStatus: {
id: 'learning.proctoringPanel.status.submitted',
defaultMessage: 'Submitted',
},
verifiedProctoringStatus: {
id: 'learning.proctoringPanel.status.verified',
defaultMessage: 'Verified',
},
rejectedProctoringStatus: {
id: 'learning.proctoringPanel.status.rejected',
defaultMessage: 'Rejected',
},
errorProctoringStatus: {
id: 'learning.proctoringPanel.status.error',
defaultMessage: 'Error',
},
proctoringCurrentStatus: {
id: 'learning.proctoringPanel.status',
defaultMessage: 'Current Onboarding Status:',
},
notStartedProctoringMessage: {
id: 'learning.proctoringPanel.message.notStarted',
defaultMessage: 'You have not started your onboarding exam.',
},
startedProctoringMessage: {
id: 'learning.proctoringPanel.message.started',
defaultMessage: 'You have started your onboarding exam.',
},
submittedProctoringMessage: {
id: 'learning.proctoringPanel.message.submitted',
defaultMessage: 'You have submitted your onboarding exam.',
},
verifiedProctoringMessage: {
id: 'learning.proctoringPanel.message.verified',
defaultMessage: 'You can now take proctored exams in this course.',
},
rejectedProctoringMessage: {
id: 'learning.proctoringPanel.message.rejected',
defaultMessage: 'Your onboarding exam has been rejected. Please retry onboarding.',
},
errorProctoringMessage: {
id: 'learning.proctoringPanel.message.error',
defaultMessage: 'An error has occurred during your onboarding exam. Please retry onboarding.',
},
proctoringPanelGeneralInfo: {
id: 'learning.proctoringPanel.generalInfo',
defaultMessage: 'You must complete the onboarding process prior to taking any proctored exam. ',
},
proctoringPanelGeneralTime: {
id: 'learning.proctoringPanel.generalTime',
defaultMessage: 'Onboarding profile review, including identity verification, can take 2+ business days.',
},
proctoringOnboardingButton: {
id: 'learning.proctoringPanel.onboardingButton',
defaultMessage: 'Complete Onboarding',
},
proctoringReviewRequirementsButton: {
id: 'learning.proctoringPanel.reviewRequirementsButton',
defaultMessage: 'Review instructions and system requirements',
},
});
export default messages;

View File

@@ -0,0 +1,99 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button } from '@edx/paragon';
import messages from '../messages';
import { getProctoringInfoData } from '../../data/api';
function ProctoringInfoPanel({ courseId, intl }) {
const [status, setStatus] = useState('');
const [link, setLink] = useState('');
const [readableStatus, setReadableStatus] = useState('');
function getReadableStatusClass(examStatus) {
let readableClass = '';
if (['created', 'download_software_clicked', 'ready_to_start'].includes(examStatus) || !examStatus) {
readableClass = 'notStarted';
} else if (['started', 'ready_to_submit'].includes(examStatus)) {
readableClass = 'started';
} else if (['second_review_required', 'submitted'].includes(examStatus)) {
readableClass = 'submitted';
} else if (['verified', 'rejected', 'error'].includes(examStatus)) {
readableClass = examStatus;
}
return readableClass;
}
function showExamLink(examStatus) {
const NO_SHOW_STATES = ['submitted', 'second_review_required', 'verified'];
return !NO_SHOW_STATES.includes(examStatus);
}
function getBorderClass(examStatus) {
let borderClass = '';
if (['submitted', 'second_review_required'].includes(examStatus)) {
borderClass = 'proctoring-onboarding-submitted';
} else if (examStatus === 'verified') {
borderClass = 'proctoring-onboarding-success';
}
return borderClass;
}
useEffect(() => {
getProctoringInfoData(courseId)
.then(
response => {
if (response) {
setStatus(response.onboarding_status);
setLink(response.onboarding_link);
setReadableStatus(getReadableStatusClass(response.onboarding_status));
}
},
);
}, []);
return (
<>
{ status && (
<section className={`mb-4 p-3 outline-sidebar-proctoring-panel ${getBorderClass(status)}`}>
<h2 className="h4" id="outline-sidebar-upgrade-header">{intl.formatMessage(messages.proctoringInfoPanel)}</h2>
<div>
{readableStatus && (
<>
<p className="h6">
{intl.formatMessage(messages.proctoringCurrentStatus)} {intl.formatMessage(messages[`${readableStatus}ProctoringStatus`])}
</p>
<p>
{intl.formatMessage(messages[`${readableStatus}ProctoringMessage`])}
</p>
</>
)}
{(readableStatus !== 'verified') && (
<>
<p>{intl.formatMessage(messages.proctoringPanelGeneralInfo)}</p>
<p>{intl.formatMessage(messages.proctoringPanelGeneralTime)}</p>
</>
)}
{showExamLink(status) && (
<Button variant="primary" block href={link}>
{intl.formatMessage(messages.proctoringOnboardingButton)}
</Button>
)}
<Button variant="outline-primary" block href="https://support.edx.org/hc/en-us/sections/115004169247-Taking-Timed-and-Proctored-Exams">
{intl.formatMessage(messages.proctoringReviewRequirementsButton)}
</Button>
</div>
</section>
)}
</>
);
}
ProctoringInfoPanel.propTypes = {
courseId: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};
export default injectIntl(ProctoringInfoPanel);

View File

@@ -0,0 +1,10 @@
.outline-sidebar-proctoring-panel {
border: 1px solid $dark-500;
border-top: 5px solid $brand-600;
}
.proctoring-onboarding-success {
border-top: 5px solid $primary-500;
}
.proctoring-onboarding-submitted {
border-top: 5px solid $dark-500;
}

View File

@@ -366,3 +366,4 @@
@import 'course-home/dates-tab/Badge.scss';
@import 'course-home/dates-tab/Day.scss';
@import 'course-home/outline-tab/widgets/UpgradeCard.scss';
@import 'course-home/outline-tab/widgets/ProctoringInfoPanel.scss';