Skip to content

Commit

Permalink
Merge pull request #2402 from drgrice1/problem-grader-use-last-checke…
Browse files Browse the repository at this point in the history
…d-score

Add a "Use score from last check" button to the single problem grader.
  • Loading branch information
pstaabp committed May 15, 2024
2 parents bcd6794 + 3257876 commit 3de656d
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 248 deletions.
203 changes: 1 addition & 202 deletions htdocs/js/ProblemGrader/problemgrader.js
Original file line number Diff line number Diff line change
@@ -1,211 +1,10 @@
'use strict';

(() => {
const setPointInputValue = (pointInput, score) =>
(pointInput.value = (Math.round((score * pointInput.max) / 100 / pointInput.step) * pointInput.step).toFixed(
2
));

// Compute the problem score from any answer sub scores, and update the problem score input.
document.querySelectorAll('.answer-part-score').forEach((part) => {
part.addEventListener('input', () => {
const problemId = part.dataset.problemId;
const answerLabels = JSON.parse(part.dataset.answerLabels);

if (!part.checkValidity()) {
part.classList.add('is-invalid');
} else {
part.classList.remove('is-invalid');

let score = 0;
answerLabels.forEach((label) => {
const partElt = document.getElementById(`score_problem${problemId}_${label}`);
score += partElt.value * partElt.dataset.weight;
});
document.getElementById(`score_problem${problemId}`).value = Math.round(score);

const pointInput = document.getElementById(`score_problem${problemId}_points`);
if (pointInput) setPointInputValue(pointInput, score);
}
document.getElementById(`grader_messages_problem${problemId}`).innerHTML = '';
});
});

// Update problem score if point value changes and is a valid value.
document.querySelectorAll('.problem-points').forEach((pointInput) => {
pointInput.addEventListener('input', () => {
const problemId = pointInput.dataset.problemId;
if (pointInput.checkValidity()) {
const scoreInput = document.getElementById(`score_problem${problemId}`);
pointInput.classList.remove('is-invalid');
scoreInput.classList.remove('is-invalid');
scoreInput.value = Math.round((100 * pointInput.value) / pointInput.max);
} else {
pointInput.classList.add('is-invalid');
}
document.getElementById(`grader_messages_problem${problemId}`).innerHTML = '';
});
});

// Clear messages when the score or comment are changed.
document.querySelectorAll('.problem-score,.grader-problem-comment').forEach((el) => {
el.addEventListener('input', () => {
const problemId = el.dataset.problemId;
if (!el.checkValidity()) {
el.classList.add('is-invalid');
} else {
el.classList.remove('is-invalid');

if (el.classList.contains('problem-score')) {
const pointInput = document.getElementById(`score_problem${problemId}_points`);
if (pointInput) {
pointInput.classList.remove('is-invalid');
setPointInputValue(pointInput, el.value);
}
}
}
document.getElementById(`grader_messages_problem${el.dataset.problemId}`).innerHTML = '';
});
});

// Save the score and comment.
document.querySelectorAll('.save-grade').forEach((saveButton) => {
saveButton.addEventListener('click', async () => {
const saveData = saveButton.dataset;

const authenParams = {};
const user = document.getElementsByName('user')[0];
if (user) authenParams.user = user.value;
const sessionKey = document.getElementsByName('key')[0];
if (sessionKey) authenParams.key = sessionKey.value;

const messageArea = document.getElementById(`grader_messages_problem${saveData.problemId}`);

const scoreInput = document.getElementById('score_problem' + saveData.problemId);
if (!scoreInput.checkValidity()) {
messageArea.classList.add('alert-danger');
messageArea.textContent = scoreInput.validationMessage;
setTimeout(() => messageArea.classList.remove('alert-danger'), 100);
scoreInput.focus();
return;
}

// Save the score.
const basicWebserviceURL = `${webworkConfig?.webwork_url ?? '/webwork2'}/instructor_rpc`;

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);

try {
const response = await fetch(basicWebserviceURL, {
method: 'post',
mode: 'same-origin',
body: new URLSearchParams({
...authenParams,
rpc_command: saveData.versionId !== '0' ? 'putProblemVersion' : 'putUserProblem',
courseID: saveData.courseId,
user_id: saveData.studentId,
set_id: saveData.setId,
version_id: saveData.versionId,
problem_id: saveData.problemId,
status: parseInt(scoreInput.value) / 100,
mark_graded: true
}),
signal: controller.signal
});

clearTimeout(timeoutId);

if (!response.ok) {
throw 'Unknown server communication error.';
} else {
const data = await response.json();
if (data.error) {
throw data.error;
} else {
// Update the hidden problem status fields and score table for gateway quizzes
if (saveData.versionId !== '0') {
const probStatus = document.gwquiz.elements[`probstatus${saveData.problemId}`];
if (probStatus) probStatus.value = parseInt(scoreInput.value) / 100;
let testValue = 0;
for (const scoreCell of document.querySelectorAll('table.gwNavigation td.score')) {
if (scoreCell.dataset.problemId == saveData.problemId) {
scoreCell.textContent = scoreInput.value == '100' ? '\u{1F4AF}' : scoreInput.value;
}
testValue +=
(document.gwquiz.elements[`probstatus${scoreCell.dataset.problemId}`]?.value ?? 0) *
scoreCell.dataset.problemValue;
}
const recordedScore = document.getElementById('test-recorded-score');
if (recordedScore) {
recordedScore.textContent = Math.round((100 * testValue) / 2) / 100;
document.getElementById('test-recorded-percent').textContent = Math.round(
(100 * testValue) / (2 * document.getElementById('test-total-possible').textContent)
);
}
}

if (saveData.pastAnswerId !== '0') {
// Save the comment.
const comment = document.getElementById(`comment_problem${saveData.problemId}`)?.value;

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);

try {
const response = await fetch(basicWebserviceURL, {
method: 'post',
body: new URLSearchParams({
...authenParams,
rpc_command: 'putPastAnswer',
courseID: saveData.courseId,
answer_id: saveData.pastAnswerId,
comment_string: comment
}),
signal: controller.signal
});

clearTimeout(timeoutId);

if (!response.ok) {
throw 'Unknown server communication error.';
} else {
const data = await response.json();
if (data.error) {
throw data.error;
} else {
messageArea.classList.add('alert-success');
messageArea.textContent = 'Score and comment saved.';
setTimeout(() => messageArea.classList.remove('alert-success'), 100);
}
}
} catch (e) {
messageArea.classList.add('alert-danger');
messageArea.innerHTML =
'<div>The score was saved, but there was an error saving the comment.</div>' +
`<div>${e}</div>`;
setTimeout(() => messageArea.classList.remove('alert-danger'), 100);
}
} else {
messageArea.classList.add('alert-success');
messageArea.textContent = 'Score saved.';
setTimeout(() => messageArea.classList.remove('alert-success'), 100);
}
}
}
} catch (e) {
messageArea.classList.add('alert-danger');
messageArea.innerHTML = `<div>Error saving score.</div><div>${e?.message ?? e}</div>`;
setTimeout(() => messageArea.classList.remove('alert-danger'), 100);
}
});
});

// Problem rendering.

const userSelect = document.getElementById('student_selector');
if (!userSelect) return;

// Problem rendering.
const render = () => {
const selectedUser = userSelect.options[userSelect.selectedIndex];
if (!selectedUser) return;
Expand Down

0 comments on commit 3de656d

Please sign in to comment.