Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: pollAttempt call on download page #113

Merged
merged 3 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/data/__snapshots__/redux.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ Object {
"in_timed_exam": true,
"software_download_url": "",
"taking_as_proctored": false,
"time_remaining_seconds": 1739.9,
"time_remaining_seconds": 1799.9,
"total_time": "30 minutes",
}
`;
Expand Down
26 changes: 21 additions & 5 deletions src/data/api.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logError } from '@edx/frontend-platform/logging';
import { ExamAction } from '../constants';
import { generateHumanizedTime } from '../helpers';

const BASE_API_URL = '/api/edx_proctoring/v1/proctored_exam/attempt';

async function fetchActiveAttempt() {
// fetch 'active' (timer is running) attempt if it exists
const activeAttemptUrl = new URL(`${getConfig().EXAMS_BASE_URL}/api/v1/exams/attempt/latest`);
const activeAttemptResponse = await getAuthenticatedHttpClient().get(activeAttemptUrl.href);
return activeAttemptResponse.data;
}

async function fetchLatestExamAttempt(sequenceId) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

basically the same thing as the above function. I broke this out to make reading the behavior a bit easier

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth adding a comment noting this so that people don't confused by these functions having similar functionality later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea 👍

// the calls the same endpoint as fetchActiveAttempt but it behaves slightly different
// with an exam's section specified. The attempt for that requested exam is always returned
// even if it is not 'active' (timer is not running)
const attemptUrl = new URL(`${getConfig().EXAMS_BASE_URL}/api/v1/exams/attempt/latest`);
attemptUrl.searchParams.append('content_id', sequenceId);
const response = await getAuthenticatedHttpClient().get(attemptUrl.href);
return response.data;
}

export async function fetchExamAttemptsData(courseId, sequenceId) {
let data;
if (!getConfig().EXAMS_BASE_URL) {
Expand Down Expand Up @@ -56,22 +68,26 @@ export async function fetchLatestAttempt(courseId) {
return data;
}

export async function pollExamAttempt(url) {
export async function pollExamAttempt(pollUrl, sequenceId) {
let data;
if (!getConfig().EXAMS_BASE_URL) {
if (pollUrl) {
const edxProctoringURL = new URL(
`${getConfig().LMS_BASE_URL}${url}`,
`${getConfig().LMS_BASE_URL}${pollUrl}`,
);
const urlResponse = await getAuthenticatedHttpClient().get(edxProctoringURL.href);
data = urlResponse.data;
} else {
data = await fetchActiveAttempt();
} else if (sequenceId && getConfig().EXAMS_BASE_URL) {
data = await fetchLatestExamAttempt(sequenceId);

// Update dictionaries returned by edx-exams to have correct status key for legacy compatibility
if (data.attempt_status) {
data.status = data.attempt_status;
delete data.attempt_status;
}
} else {
// sites configured with only edx-proctoring must have pollUrl set
// sites configured with edx-exams expect sequenceId if pollUrl is not set
logError(`pollExamAttempt recieved unexpected parameters pollUrl=${pollUrl} sequenceId=${sequenceId}`);
}
return data;
}
Expand Down
30 changes: 30 additions & 0 deletions src/data/redux.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import MockAdapter from 'axios-mock-adapter';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig, mergeConfig } from '@edx/frontend-platform';

import * as api from './api';
import * as thunks from './thunks';

import executeThunk from '../utils';
Expand Down Expand Up @@ -958,6 +959,35 @@ describe('Data layer integration tests', () => {
const state = store.getState();
expect(state.examState.activeAttempt).toMatchSnapshot();
});

describe('pollAttempt api called directly', () => {
// in the download view we call this function directly withough invoking the wrapping thunk
it('should call pollUrl if one is provided', async () => {
const pollExamAttemptUrl = `${getConfig().LMS_BASE_URL}${attempt.exam_started_poll_url}`;
axiosMock.onGet(pollExamAttemptUrl).reply(200, {
time_remaining_seconds: 1739.9,
accessibility_time_string: 'you have 29 minutes remaining',
attempt_status: ExamStatus.STARTED,
});
await api.pollExamAttempt(attempt.exam_started_poll_url);
expect(axiosMock.history.get[0].url).toEqual(pollExamAttemptUrl);
});
it('should call the latest attempt for a sequence if a sequence id is provided instead of a pollUrl', async () => {
const sequenceId = 'block-v1:edX+Test+123';
const expectedUrl = `${getConfig().EXAMS_BASE_URL}/api/v1/exams/attempt/latest?content_id=${encodeURIComponent(sequenceId)}`;
axiosMock.onGet(expectedUrl).reply(200, {
time_remaining_seconds: 1739.9,
status: ExamStatus.STARTED,
});
await api.pollExamAttempt(null, sequenceId);
expect(axiosMock.history.get[0].url).toEqual(expectedUrl);
});
test('pollUrl is required if edx-exams in not enabled, an error should be logged', async () => {
mergeConfig({ EXAMS_BASE_URL: null });
api.pollExamAttempt(null, null);
expect(loggingService.logError).toHaveBeenCalled();
});
});
});

describe('Test pingAttempt', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const DownloadSoftwareProctoredExamInstructions = ({ intl, skipProctoredExam })
? `${getConfig().EXAMS_BASE_URL}/lti/start_proctoring/${attemptId}` : downloadUrl;

const handleDownloadClick = () => {
pollExamAttempt(`${pollUrl}?sourceid=instructions`)
pollExamAttempt(pollUrl, sequenceId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sourceid=instructions doesn't do anything. Was just copied behavior from the old frontend.

.then((data) => {
if (data.status === ExamStatus.READY_TO_START) {
setSystemCheckStatus('success');
Expand All @@ -66,7 +66,7 @@ const DownloadSoftwareProctoredExamInstructions = ({ intl, skipProctoredExam })
};

const handleStartExamClick = () => {
pollExamAttempt(`${pollUrl}?sourceid=instructions`)
pollExamAttempt(pollUrl, sequenceId)
.then((data) => (
data.status === ExamStatus.READY_TO_START
? getExamAttemptsData(courseId, sequenceId)
Expand Down
Loading