MST-334 Implement the settings post back functionality (#16)

* MST-334 Implement the settings post back functionality

* fix

* take in feedback

* feedback
This commit is contained in:
Simon Chen
2020-07-24 16:46:53 -04:00
committed by GitHub
parent 171a054ce5
commit 9352ab3178
3 changed files with 157 additions and 3 deletions

View File

@@ -8,11 +8,25 @@ ensureConfig([
const studioBaseUrl = getConfig().STUDIO_BASE_URL;
class StudioApiService {
static getProctoredExamSettingsUrl(courseID) {
return `${studioBaseUrl}/api/contentstore/v1/proctored_exam_settings/${courseID}`;
}
static getProctoredExamSettingsData(courseID) {
const apiClient = getAuthenticatedHttpClient();
const url = `${studioBaseUrl}/api/contentstore/v1/proctored_exam_settings/${courseID}`;
const url = StudioApiService.getProctoredExamSettingsUrl(courseID);
return apiClient.get(url);
}
static saveProctoredExamSettingsData(courseID, dataToSave) {
const apiClient = getAuthenticatedHttpClient();
const url = StudioApiService.getProctoredExamSettingsUrl(courseID);
return apiClient.post(url, dataToSave);
}
static getStudioCourseRunUrl(courseID) {
return `${studioBaseUrl}/course/${courseID}`;
}
}
export default StudioApiService;

View File

@@ -24,6 +24,8 @@ function ExamSettings(props) {
const [createZendeskTickets, setCreateZendeskTickets] = useState(false);
const [proctortrackEscalationEmailError, setProctortrackEscalationEmailError] = useState('');
const [courseStartDate, setCourseStartDate] = useState('');
const [saveSuccess, setSaveSuccess] = useState(false);
const [saveError, setSaveError] = useState(false);
function onEnableProctoredExamsChange(event) {
setEnableProctoredExams(event.target.checked);
@@ -52,13 +54,32 @@ function ExamSettings(props) {
setProctortrackEscalationEmail(event.target.value);
}
function postSettingsBackToServer() {
const dataToPostBack = {
proctored_exam_settings: {
enable_proctored_exams: enableProctoredExams,
allow_proctoring_opt_out: allowOptingOut,
proctoring_provider: proctoringProvider,
proctoring_escalation_email: proctortrackEscalationEmail,
create_zendesk_tickets: createZendeskTickets,
},
};
StudioApiService.saveProctoredExamSettingsData(props.courseId, dataToPostBack).then(() => {
setSaveSuccess(true);
setSaveError(false);
}).catch(() => {
setSaveSuccess(false);
setSaveError(true);
});
}
function onButtonClick() {
if (proctoringProvider === 'proctortrack' && !EmailValidator.validate(proctortrackEscalationEmail)) {
setProctortrackEscalationEmailError('A valid escalation email must be provided if '
+ 'Proctortrack is the selected provider.');
} else {
setProctortrackEscalationEmailError('');
// TODO: implement POST
postSettingsBackToServer();
}
}
@@ -165,6 +186,7 @@ function ExamSettings(props) {
<Form.Label>Proctortrack Escalation Email</Form.Label>
<Form.Control
type="email"
data-test-id="escalationEmail"
onChange={onProctortrackEscalationEmailChange}
value={proctortrackEscalationEmail}
isInvalid={!!proctortrackEscalationEmailError}
@@ -208,6 +230,7 @@ function ExamSettings(props) {
</fieldset>
<Button
className="btn-primary mb-3"
data-test-id="submissionButton"
onClick={onButtonClick}
>
Submit
@@ -237,7 +260,8 @@ function ExamSettings(props) {
<Alert variant="danger" data-test-id="connectionError">
We encountered a technical error when loading this page.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists, please go to <a href="https://support.edx.org/hc/en-us">edX Support Page</a> for help.
If the problem persists,
please go to <Alert.Link href="https://support.edx.org/hc/en-us">edX Support Page</Alert.Link> for help.
</Alert>
);
}
@@ -251,6 +275,37 @@ function ExamSettings(props) {
);
}
function renderSaveSuccess() {
const studioCourseRunURL = StudioApiService.getStudioCourseRunUrl(props.courseId);
return (
<Alert
variant="success"
dismissible
data-test-id="saveSuccess"
onClose={() => setSaveSuccess(false)}
>
Proctored exam settings saved successfully.
You can go back to your course in Studio <Alert.Link href={studioCourseRunURL}>here</Alert.Link>.
</Alert>
);
}
function renderSaveError() {
return (
<Alert
variant="danger"
dismissible
data-test-id="saveError"
onClose={() => setSaveError(false)}
>
We encountered a technical error while trying to save proctored exam settings.
This might be a temporary issue, so please try again in a few minutes.
If the problem persists,
please go to <Alert.Link href="https://support.edx.org/hc/en-us">edX Support Page</Alert.Link> for help.
</Alert>
);
}
useEffect(
() => {
StudioApiService.getProctoredExamSettingsData(props.courseId)
@@ -288,6 +343,8 @@ function ExamSettings(props) {
</h2>
<div>
{loading ? renderLoading() : null}
{saveSuccess ? renderSaveSuccess() : null}
{saveError ? renderSaveError() : null}
{loaded ? renderContent() : null}
{loadingConnectionError ? renderConnectionError() : null}
{loadingPermissionError ? renderPermissionError() : null}

View File

@@ -4,6 +4,7 @@ import {
} from '@testing-library/react';
import * as auth from '@edx/frontend-platform/auth';
import ProctoredExamSettings from './ProctoredExamSettings';
import StudioApiService from '../data/services/StudioApiService';
const defaultProps = {
courseId: 'course-v1%3AedX%2BDemoX%2BDemo_Course',
@@ -123,3 +124,85 @@ describe('ProctoredExamSettings connection states tests', () => {
cleanup();
});
});
describe('ProctoredExamSettings save settings tests', () => {
const mockGetData = {
data: {
proctored_exam_settings: {
enable_proctored_exams: true,
allow_proctoring_opt_out: false,
proctoring_provider: 'mockproc',
proctoring_escalation_email: 'test@example.com',
create_zendesk_tickets: true,
},
available_proctoring_providers: ['software_secure', 'proctortrack', 'mockproc'],
},
};
function mockAPI(getData, postResult) {
const mockClientGet = jest.fn(async () => (getData));
const mockClientPost = postResult ? jest.fn(async () => (postResult)) : jest.fn(async () => { throw new Error(); });
auth.getAuthenticatedHttpClient = jest.fn(() => ({
get: mockClientGet,
post: mockClientPost,
}));
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 }));
return { mockClientGet, mockClientPost };
}
it('Makes API call successfully', async () => {
const mockedFunctions = mockAPI(mockGetData, { data: 'success' });
await act(async () => render(<ProctoredExamSettings {...defaultProps} />));
// Make a change to the provider to proctortrack and set the email
const selectElement = screen.getByDisplayValue('mockproc');
await act(async () => {
fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
});
const escalationEmail = screen.getByTestId('escalationEmail');
expect(escalationEmail.value).toEqual('test@example.com');
await act(async () => {
fireEvent.change(escalationEmail, { target: { value: 'proctortrack@example.com' } });
});
expect(escalationEmail.value).toEqual('proctortrack@example.com');
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
expect(mockedFunctions.mockClientPost).toHaveBeenCalled();
expect(mockedFunctions.mockClientPost).toHaveBeenCalledWith(
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
{
proctored_exam_settings: {
enable_proctored_exams: true,
allow_proctoring_opt_out: false,
proctoring_provider: 'proctortrack',
proctoring_escalation_email: 'proctortrack@example.com',
create_zendesk_tickets: false,
},
},
);
const errorAlert = screen.getByTestId('saveSuccess');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('Proctored exam settings saved successfully.'),
);
});
it('Makes API call generated error', async () => {
const mockedFunctions = mockAPI(mockGetData, false);
await act(async () => render(<ProctoredExamSettings {...defaultProps} />));
// Make a change to the provider to proctortrack and set the email
const submitButton = screen.getByTestId('submissionButton');
await act(async () => {
fireEvent.click(submitButton);
});
expect(mockedFunctions.mockClientPost).toHaveBeenCalled();
const errorAlert = screen.getByTestId('saveError');
expect(errorAlert.textContent).toEqual(
expect.stringContaining('We encountered a technical error while trying to save proctored exam settings'),
);
});
afterEach(() => {
cleanup();
});
});