This commit is contained in:
Huong Nguyen 2024-06-18 15:29:29 +07:00
commit 87ec51598d
No known key found for this signature in database
GPG Key ID: 40D88AB693A3E72A
13 changed files with 482 additions and 125 deletions

View File

@ -0,0 +1,7 @@
issueNumber: MDL-79546
notes:
quiz:
- message: >-
The functions quiz_overview_report::regrade_attempts and regrade_batch_of_attempts now
have a new optional parameter $slots to only regrade some slots in each attempt (default all).
type: improved

View File

@ -0,0 +1,10 @@
define("quiz_overview/regrade_modal",["exports","core/modal","core/templates","core/str","core/notification"],(function(_exports,_modal,_templates,_str,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Regrade modal form is used to regrade or dryrun the attempts and questions.
*
* @module quiz_overview/regrade_modal
* @copyright 2024 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification);const SELECTORS_allQuestionsButton="#regradeallquestions",SELECTORS_dryRunButton="#dryrunbutton",SELECTORS_mainTableForm="#attemptsform",SELECTORS_questionCheckbox='[id^="regradeslot"]',SELECTORS_regradeAttemptsButtonId="regradeattempts",SELECTORS_regradeButton="#regradebutton",SELECTORS_reportTableSelectedAttempts='[id^="attemptid_"]:checked',showModal=async e=>{e.preventDefault();try{let hiddenInputs=[];document.querySelectorAll(SELECTORS_mainTableForm+" input[type=hidden]").forEach((hiddenInput=>{hiddenInputs.push({name:hiddenInput.name,value:hiddenInput.value})})),document.querySelectorAll(SELECTORS_reportTableSelectedAttempts).forEach((selectedAttempt=>{hiddenInputs.push({name:selectedAttempt.name,value:selectedAttempt.value})}));const modal=await _modal.default.create({title:(0,_str.getString)("regrade","quiz_overview"),body:_templates.default.render("quiz_overview/regrade_modal_body",{actionurl:document.querySelector(SELECTORS_mainTableForm).action,hasselectedattempts:null!==document.querySelector(SELECTORS_reportTableSelectedAttempts),questions:JSON.parse(document.getElementById(SELECTORS_regradeAttemptsButtonId).dataset.slots),hiddeninputs:hiddenInputs}),isVerticallyCentered:!0,removeOnClose:!0,show:!0});modal.getRoot()[0].addEventListener("change",updateButtonStates),modal.getBodyPromise().then(updateButtonStates).catch(_notification.default.exception),modal.getTitlePromise().then((title=>(title.append(" "+document.getElementById(SELECTORS_regradeAttemptsButtonId).dataset.helpIcon),title[0].querySelector("a").classList.add("align-baseline"),title[0]))).catch(_notification.default.exception)}catch(ex){await _notification.default.exception(ex)}},updateButtonStates=()=>{const allQuestionsButton=document.querySelector(SELECTORS_allQuestionsButton);document.querySelectorAll(SELECTORS_questionCheckbox).forEach((questionCheckbox=>{questionCheckbox.disabled=allQuestionsButton.checked}));const isAnyQuestionSelected=null!==document.querySelector(SELECTORS_questionCheckbox+":checked"),canSubmit=allQuestionsButton.checked||isAnyQuestionSelected;document.querySelector(SELECTORS_regradeButton).disabled=!canSubmit,document.querySelector(SELECTORS_dryRunButton).disabled=!canSubmit};_exports.init=()=>{const regradeButton=document.getElementById(SELECTORS_regradeAttemptsButtonId);regradeButton&&regradeButton.addEventListener("click",showModal)}}));
//# sourceMappingURL=regrade_modal.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,113 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Regrade modal form is used to regrade or dryrun the attempts and questions.
*
* @module quiz_overview/regrade_modal
* @copyright 2024 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Modal from 'core/modal';
import Templates from 'core/templates';
import {getString} from 'core/str';
import Notification from 'core/notification';
/**
* @type {Object} selectors used in this code.
*/
const SELECTORS = {
'allQuestionsButton': '#regradeallquestions',
'dryRunButton': '#dryrunbutton',
'mainTableForm': '#attemptsform',
'questionCheckbox': '[id^="regradeslot"]',
'regradeAttemptsButtonId': 'regradeattempts',
'regradeButton': '#regradebutton',
'reportTableSelectedAttempts': '[id^="attemptid_"]:checked',
};
/**
* Show the regrade modal.
*
* @param {Event} e click event that opened the modal.
* @returns {Promise<void>}
*/
const showModal = async(e) => {
e.preventDefault();
try {
let hiddenInputs = [];
document.querySelectorAll(SELECTORS.mainTableForm + ' input[type=hidden]').forEach((hiddenInput) => {
hiddenInputs.push({'name': hiddenInput.name, 'value': hiddenInput.value});
});
document.querySelectorAll(SELECTORS.reportTableSelectedAttempts).forEach((selectedAttempt) => {
hiddenInputs.push({'name': selectedAttempt.name, 'value': selectedAttempt.value});
});
const modal = await Modal.create({
title: getString('regrade', 'quiz_overview'),
body: Templates.render('quiz_overview/regrade_modal_body', {
'actionurl': document.querySelector(SELECTORS.mainTableForm).action,
'hasselectedattempts': document.querySelector(SELECTORS.reportTableSelectedAttempts) !== null,
'questions': JSON.parse(document.getElementById(SELECTORS.regradeAttemptsButtonId).dataset.slots),
'hiddeninputs': hiddenInputs,
}),
isVerticallyCentered: true,
removeOnClose: true,
show: true,
});
modal.getRoot()[0].addEventListener('change', updateButtonStates);
modal.getBodyPromise().then(updateButtonStates).catch(Notification.exception);
// TODO MDL-82204 - there is not currently a good way to add a help icon to a modal overall, so we do it this way.
modal.getTitlePromise().then((title) => {
title.append(' ' + document.getElementById(SELECTORS.regradeAttemptsButtonId).dataset.helpIcon);
// The next line is necessary to get a nice layout of the help icon.
title[0].querySelector('a').classList.add('align-baseline');
return title[0];
}).catch(Notification.exception);
} catch (ex) {
await Notification.exception(ex);
}
};
/**
* Enables and disables controls when the selected options are changed.
*/
const updateButtonStates = () => {
const allQuestionsButton = document.querySelector(SELECTORS.allQuestionsButton);
// Question checkboxes enabled only if Selected questions is checked.
document.querySelectorAll(SELECTORS.questionCheckbox).forEach((questionCheckbox) => {
questionCheckbox.disabled = allQuestionsButton.checked;
});
// State of submit buttons.
const isAnyQuestionSelected = document.querySelector(SELECTORS.questionCheckbox + ':checked') !== null;
const canSubmit = allQuestionsButton.checked || isAnyQuestionSelected;
document.querySelector(SELECTORS.regradeButton).disabled = !canSubmit;
document.querySelector(SELECTORS.dryRunButton).disabled = !canSubmit;
};
/**
* Initialize the regrade button to open the modal.
*/
export const init = () => {
const regradeButton = document.getElementById(SELECTORS.regradeAttemptsButtonId);
if (!regradeButton) {
return;
}
regradeButton.addEventListener('click', showModal);
};

View File

@ -0,0 +1,7 @@
regradeall,quiz_overview
regradealldry,quiz_overview
regradealldrydo,quiz_overview
regradealldrydogroup,quiz_overview
regradealldrygroup,quiz_overview
regradeallgroup,quiz_overview
regradeselected,quiz_overview

View File

@ -57,16 +57,20 @@ $string['privacy:metadata:preference:quiz_overview_slotmarks'] = 'Whether to sho
$string['privacy:preference:slotmarks:yes'] = 'Marks are shown alongside the question slot.';
$string['privacy:preference:slotmarks:no'] = 'Marks are not shown alongside the question slot.';
$string['regrade'] = 'Regrade';
$string['regradeall'] = 'Regrade all';
$string['regradealldry'] = 'Dry run a full regrade';
$string['regradealldrydo'] = 'Regrade attempts marked as needing regrading ({$a})';
$string['regradealldrydogroup'] = 'Regrade attempts ({$a->countregradeneeded}) marked as needing regrading in group \'{$a->groupname}\'';
$string['regradealldrygroup'] = 'Dry run a full regrade for group \'{$a->groupname}\'';
$string['regradeallgroup'] = 'Full regrade for group \'{$a->groupname}\'';
$string['regrade_allattempts'] = 'All attempts';
$string['regrade_allquestions'] = 'All questions';
$string['regrade_attempts'] = 'Regrade attempts...';
$string['regrade_commitregrade'] = 'Commit regrade';
$string['regrade_dryrun'] = 'Dry run';
$string['regrade_help'] = 'Regrading recalculates the automated grading of attempts, using the current version of each question if possible. A dry-run allows you to preview the grade changes without affecting attempts.';
$string['regrade_link'] = 'Quiz_grades_report#Re-grading_attempts';
$string['regrade_regradeneedednotificationmessage'] = 'The last dry-run of a regrade found that the re-grade would change the marks for {$a->questions} questions in {$a->attempts} attempts.';
$string['regrade_regradenow'] = 'Regrade now';
$string['regrade_selectedattempts'] = 'Selected attempts';
$string['regrade_selectedquestions'] = 'Selected questions';
$string['regradecomplete'] = 'Regrade completed';
$string['regradedsuccessfullyxofy'] = 'Finished regrading ({$a->done}/{$a->count})';
$string['regradeheader'] = 'Regrading';
$string['regradeselected'] = 'Regrade selected attempts';
$string['regradingattemptissue'] = 'Slot {$a->slot}: {$a->reason}';
$string['regradingattemptxofy'] = 'Regrading attempt ({$a->done}/{$a->count})';
$string['regradingattemptxofyproblem'] = 'The following questions could not be regraded in attempt {$a->attemptnum} by {$a->name} (id {$a->attemptid})';
@ -78,3 +82,12 @@ $string['showinggraded'] = 'Showing only the attempt graded for each user.';
$string['showinggradedandungraded'] = 'Showing graded and ungraded attempts for each user. The one attempt for each user that is graded is highlighted. The grading method for this quiz is {$a}.';
$string['studentingroup'] = '\'{$a->coursestudent}\' in group \'{$a->groupname}\'';
$string['studentingrouplong'] = '\'{$a->coursestudent}\' in this group';
// Deprecated since Moodle 4.4.
$string['regradeall'] = 'Regrade all';
$string['regradealldry'] = 'Dry run a full regrade';
$string['regradealldrydo'] = 'Regrade attempts marked as needing regrading ({$a})';
$string['regradealldrydogroup'] = 'Regrade attempts ({$a->countregradeneeded}) marked as needing regrading in group \'{$a->groupname}\'';
$string['regradealldrygroup'] = 'Dry run a full regrade for group \'{$a->groupname}\'';
$string['regradeallgroup'] = 'Full regrade for group \'{$a->groupname}\'';
$string['regradeselected'] = 'Regrade selected attempts';

View File

@ -204,20 +204,28 @@ class quiz_overview_table extends attempts_report_table {
}
protected function submit_buttons() {
if (has_capability('mod/quiz:regrade', $this->context)) {
$regradebuttonparams = [
'type' => 'submit',
'class' => 'btn btn-secondary mr-1',
'name' => 'regrade',
'value' => get_string('regradeselected', 'quiz_overview'),
'data-action' => 'toggle',
'data-togglegroup' => $this->togglegroup,
'data-toggle' => 'action',
'disabled' => true
];
echo html_writer::empty_tag('input', $regradebuttonparams);
}
global $PAGE, $OUTPUT;
parent::submit_buttons();
if (has_capability('mod/quiz:regrade', $this->context)) {
$questionslots = [];
foreach ($this->questions as $slot => $question) {
$questionslots[] = [
'slot' => $slot,
'name' => get_string('questionno', 'question', $question->number),
];
}
echo html_writer::empty_tag('input', [
'type' => 'button',
'id' => 'regradeattempts',
'class' => 'btn btn-secondary mr-1',
'name' => 'regradeattempts',
'value' => get_string('regrade_attempts', 'quiz_overview'),
'data-slots' => json_encode($questionslots),
// There is currently not a good way to render a help icon in a template, so put the required HTML here.
'data-help-icon' => $OUTPUT->help_icon('regrade', 'quiz_overview'),
]);
$PAGE->requires->js_call_amd('quiz_overview/regrade_modal', 'init');
}
}
public function col_sumgrades($attempt) {

View File

@ -25,7 +25,6 @@
use mod_quiz\local\reports\attempts_report;
use mod_quiz\question\bank\qbank_helper;
use mod_quiz\quiz_attempt;
use mod_quiz\quiz_settings;
defined('MOODLE_INTERNAL') || die();
@ -43,7 +42,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_table.php');
class quiz_overview_report extends attempts_report {
public function display($quiz, $cm, $course) {
global $DB, $PAGE;
global $DB, $PAGE, $OUTPUT;
list($currentgroup, $studentsjoins, $groupstudentsjoins, $allowedjoins) = $this->init(
'overview', 'quiz_overview_settings_form', $quiz, $cm, $course);
@ -93,7 +92,7 @@ class quiz_overview_report extends attempts_report {
if ($options->attempts == self::ALL_WITH) {
// This option is only available to users who can access all groups in
// groups mode, so setting allowed to empty (which means all quiz attempts
// are accessible, is not a security porblem.
// are accessible, is not a security problem.
$allowedjoins = new \core\dml\sql_join();
}
@ -115,49 +114,7 @@ class quiz_overview_report extends attempts_report {
if ($hasquestions && ($hasstudents || $options->attempts == self::ALL_WITH)) {
// Construct the SQL.
$table->setup_sql_queries($allowedjoins);
if (!$table->is_downloading()) {
// Output the regrade buttons.
if (has_capability('mod/quiz:regrade', $this->context)) {
$regradesneeded = $this->count_question_attempts_needing_regrade(
$quiz, $groupstudentsjoins);
if ($currentgroup) {
$a= new stdClass();
$a->groupname = format_string(groups_get_group_name($currentgroup), true, [
'context' => $this->context,
]);
$a->coursestudents = get_string('participants');
$a->countregradeneeded = $regradesneeded;
$regradealldrydolabel =
get_string('regradealldrydogroup', 'quiz_overview', $a);
$regradealldrylabel =
get_string('regradealldrygroup', 'quiz_overview', $a);
$regradealllabel =
get_string('regradeallgroup', 'quiz_overview', $a);
} else {
$regradealldrydolabel =
get_string('regradealldrydo', 'quiz_overview', $regradesneeded);
$regradealldrylabel =
get_string('regradealldry', 'quiz_overview');
$regradealllabel =
get_string('regradeall', 'quiz_overview');
}
$displayurl = new moodle_url($options->get_url(), ['sesskey' => sesskey()]);
echo '<div class="regradebuttons">';
echo '<form action="'.$displayurl->out_omit_querystring().'">';
echo '<div>';
echo html_writer::input_hidden_params($displayurl);
echo '<input type="submit" class="btn btn-secondary" name="regradeall" value="'.$regradealllabel.'"/>';
echo '<input type="submit" class="btn btn-secondary ml-1" name="regradealldry" value="' .
$regradealldrylabel . '"/>';
if ($regradesneeded) {
echo '<input type="submit" class="btn btn-secondary ml-1" name="regradealldrydo" value="' .
$regradealldrydolabel . '"/>';
}
echo '</div>';
echo '</form>';
echo '</div>';
}
// Print information on the grading method.
if ($strattempthighlight = quiz_report_highlighting_grading_method(
$quiz, $this->qmsubselect, $options->onlygraded)) {
@ -182,7 +139,8 @@ class quiz_overview_report extends attempts_report {
$this->add_grade_columns($quiz, $options->usercanseegrades, $columns, $headers, false);
$this->add_grade_item_columns($options->usercanseegrades, $columns, $headers);
if (!$table->is_downloading() && has_capability('mod/quiz:regrade', $this->context) &&
$canregrade = has_capability('mod/quiz:regrade', $this->context);
if (!$table->is_downloading() && $canregrade &&
$this->has_regraded_questions($table->sql->from, $table->sql->where, $table->sql->params)) {
$columns[] = 'regraded';
$headers[] = get_string('regrade', 'quiz_overview');
@ -206,6 +164,10 @@ class quiz_overview_report extends attempts_report {
$table->set_attribute('class', 'generaltable generalbox grades');
$table->out($options->pagesize, true);
if ($canregrade && !$table->is_downloading()) {
$this->display_commit_regrade_if_required($quiz, $groupstudentsjoins, $options);
}
}
if (!$table->is_downloading() && $options->usercanseegrades) {
@ -239,9 +201,47 @@ class quiz_overview_report extends attempts_report {
echo $output->chart($chart, $graphname, ['dir' => 'ltr']);
}
}
return true;
}
/**
* If a previous dry run regrade had been done, display a message to commit the changes.
*
* @param stdClass $quiz quiz settings.
* @param \core\dml\sql_join $groupstudentsjoins which users' attempts should be considered.
* @param quiz_overview_options $options report options.
*/
protected function display_commit_regrade_if_required(
stdClass $quiz,
\core\dml\sql_join $groupstudentsjoins,
quiz_overview_options $options,
) {
global $OUTPUT;
[$attemptcount, $slotcount] = $this->count_attempts_and_questions_needing_regrade($quiz, $groupstudentsjoins);
if (!$attemptcount) {
return;
}
$commitregradeurl = new moodle_url($options->get_url(), ['sesskey' => sesskey(), 'regradealldrydo' => 1]);
// We can't use $OUTPUT->notification because is aggressively cleans the message, which strips the button.
echo html_writer::div(get_string('regrade_regradeneedednotificationmessage', 'quiz_overview',
['attempts' => $attemptcount, 'questions' => $slotcount]) . ' ' .
$OUTPUT->single_button($commitregradeurl, get_string('regrade_commitregrade', 'quiz_overview')),
'alert alert-info alert-block fade in');
}
#[\Override]
protected function process_actions($quiz, $cm, $currentgroup, \core\dml\sql_join $groupstudentsjoins,
\core\dml\sql_join $allowedjoins, $redirecturl) {
parent::process_actions($quiz, $cm, $currentgroup, $groupstudentsjoins, $allowedjoins, $redirecturl);
// Process regrade actions.
$this->process_regrade_actions($quiz, $cm, $currentgroup, $groupstudentsjoins, $redirecturl);
}
/**
* Extends parent function processing any submitted actions.
*
@ -249,34 +249,38 @@ class quiz_overview_report extends attempts_report {
* @param stdClass $cm
* @param int $currentgroup
* @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params)
* @param \core\dml\sql_join $allowedjoins (joins, wheres, params)
* @param moodle_url $redirecturl
*/
protected function process_actions($quiz, $cm, $currentgroup, \core\dml\sql_join $groupstudentsjoins,
\core\dml\sql_join $allowedjoins, $redirecturl) {
parent::process_actions($quiz, $cm, $currentgroup, $groupstudentsjoins, $allowedjoins, $redirecturl);
protected function process_regrade_actions($quiz, $cm, $currentgroup,
\core\dml\sql_join $groupstudentsjoins, moodle_url $redirecturl) {
if (empty($currentgroup) || $this->hasgroupstudents) {
if (optional_param('regrade', 0, PARAM_BOOL) && confirm_sesskey()) {
if ($attemptids = optional_param_array('attemptid', [], PARAM_INT)) {
$this->start_regrade($quiz, $cm);
$this->regrade_attempts($quiz, false, $groupstudentsjoins, $attemptids);
$this->finish_regrade($redirecturl);
}
}
if ($currentgroup && !$this->hasgroupstudents) {
return;
}
if (!has_capability('mod/quiz:regrade', $this->context)) {
return;
}
if (optional_param('regradeall', 0, PARAM_BOOL) && confirm_sesskey()) {
$this->start_regrade($quiz, $cm);
$this->regrade_attempts($quiz, false, $groupstudentsjoins);
$this->finish_regrade($redirecturl);
$dryrun = optional_param('dryrunregrade', 0, PARAM_BOOL);
if ($dryrun || optional_param('regrade', 0, PARAM_BOOL)) {
} else if (optional_param('regradealldry', 0, PARAM_BOOL) && confirm_sesskey()) {
$this->start_regrade($quiz, $cm);
$this->regrade_attempts($quiz, true, $groupstudentsjoins);
$this->finish_regrade($redirecturl);
$attemptids = [];
if (optional_param('regradeselectedattempts', 0, PARAM_BOOL)) {
$attemptids = optional_param_array('attemptid', [], PARAM_INT);
}
} else if (optional_param('regradealldrydo', 0, PARAM_BOOL) && confirm_sesskey()) {
$slots = null;
if (optional_param('regradeselectedquestions', 0, PARAM_BOOL)) {
$slots = optional_param_array('regradeslot', [], PARAM_INT);
}
$this->start_regrade($quiz, $cm);
$this->regrade_attempts($quiz, $dryrun, $groupstudentsjoins, $attemptids, $slots);
$this->finish_regrade($redirecturl);
}
// Process commit of a previous dry run.
if (optional_param('regradealldrydo', 0, PARAM_BOOL) && confirm_sesskey()) {
$this->start_regrade($quiz, $cm);
$this->regrade_attempts_needing_it($quiz, $groupstudentsjoins);
$this->finish_regrade($redirecturl);
@ -329,7 +333,7 @@ class quiz_overview_report extends attempts_report {
*
* @param stdClass $attempt the quiz attempt to regrade.
* @param bool $dryrun if true, do a pretend regrade, otherwise do it for real.
* @param array $slots if null, regrade all questions, otherwise, just regrade
* @param array|null $slots if null, regrade all questions, otherwise, just regrade
* the questions with those slots.
* @return array messages array with keys slot number, and values reasons why that slot cannot be regraded.
*/
@ -408,12 +412,15 @@ class quiz_overview_report extends attempts_report {
* for these users.
* @param array $attemptids blank for all attempts, otherwise only regrade
* attempts whose id is in this list.
* @param array|null $slots if null, regrade all questions, otherwise, just regrade
* the questions with those slots.
*/
protected function regrade_attempts($quiz, $dryrun = false,
core\dml\sql_join $groupstudentsjoins = null, $attemptids = []) {
?\core\dml\sql_join $groupstudentsjoins = null, $attemptids = [], ?array $slots = null) {
global $DB;
$this->unlock_session();
// Get the attempts to regrade.
$userfieldsapi = \core_user\fields::for_name();
$sql = "SELECT quiza.*, " . $userfieldsapi->get_sql('u', false, '', '', false)->selects . "
FROM {quiz_attempts} quiza
@ -439,15 +446,22 @@ class quiz_overview_report extends attempts_report {
return;
}
$this->regrade_batch_of_attempts($quiz, $attempts, $dryrun, $groupstudentsjoins);
// If only regrading some slots, put that information where regrade_batch_of_attempts expects.
if ($slots) {
foreach ($attempts as $attempt) {
$attempt->regradeonlyslots = $slots;
}
}
$this->regrade_batch_of_attempts($quiz, $attempts, $dryrun, $groupstudentsjoins, $slots);
}
/**
* Regrade those questions in those attempts that are marked as needing regrading
* in the quiz_overview_regrades table.
* Regrade the questions in the attempts that are marked as needing it in quiz_overview_regrades.
*
* @param stdClass $quiz the quiz settings.
* @param \core\dml\sql_join $groupstudentsjoins empty for all attempts, otherwise regrade attempts
* for these users.
* for these users.
*/
protected function regrade_attempts_needing_it($quiz, \core\dml\sql_join $groupstudentsjoins) {
global $DB;
@ -527,12 +541,9 @@ class quiz_overview_report extends attempts_report {
$a['attemptnum'] = $attempt->attempt;
$a['name'] = fullname($attempt);
$a['attemptid'] = $attempt->id;
if (!isset($attempt->regradeonlyslots)) {
$attempt->regradeonlyslots = null;
}
$progressbar->update($a['done'], $a['count'],
get_string('regradingattemptxofywithdetails', 'quiz_overview', $a));
$messages = $this->regrade_attempt($attempt, $dryrun, $attempt->regradeonlyslots);
$messages = $this->regrade_attempt($attempt, $dryrun, $attempt->regradeonlyslots ?? null);
if ($messages) {
$items = [];
foreach ($messages as $slot => $message) {
@ -553,14 +564,14 @@ class quiz_overview_report extends attempts_report {
}
/**
* Count the number of attempts in need of a regrade.
* Count the number of attempts and questions in need of regrading after the last dry run.
*
* @param stdClass $quiz the quiz settings.
* @param \core\dml\sql_join $groupstudentsjoins (joins, wheres, params) If this is given, only data relating
* to these users is cleared.
* @return int the number of attempts.
* @param \core\dml\sql_join $groupstudentsjoins which users' attempts should be considered.
* @return array of two elements: the number of different attempts and questions needed to be regraded.
*/
protected function count_question_attempts_needing_regrade($quiz, \core\dml\sql_join $groupstudentsjoins) {
protected function count_attempts_and_questions_needing_regrade($quiz,
\core\dml\sql_join $groupstudentsjoins): array {
global $DB;
$userjoin = '';
@ -574,7 +585,8 @@ class quiz_overview_report extends attempts_report {
}
$params['cquiz'] = $quiz->id;
$sql = "SELECT COUNT(DISTINCT quiza.id)
$sql = "SELECT COUNT(DISTINCT quiza.id) AS attemptcount,
COUNT(DISTINCT qqr.slot) AS slotcount
FROM {quiz_attempts} quiza
JOIN {quiz_overview_regrades} qqr ON quiza.uniqueid = qqr.questionusageid
$userjoin
@ -583,7 +595,8 @@ class quiz_overview_report extends attempts_report {
quiza.quiz = :cquiz AND
quiza.preview = 0 AND
qqr.regraded = 0";
return $DB->count_records_sql($sql, $params);
$counts = $DB->get_record_sql($sql, $params);
return [$counts->attemptcount, $counts->slotcount];
}
/**

View File

@ -0,0 +1,93 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template quiz_overview/regrade_modal_body
Example context (json):
{
"actionurl": "https://example.com/moodle/mod/quiz/report.php?mode=overview?id=123",
"hasselectedattempts": false,
"questions": [
{"slot": 3, "name": "Question 1"},
{"slot": 5, "name": "Question 2"}
],
"hiddeninputs": [
{"name": "cmid", "value": 123}
]
}
}}
<form action="{{actionurl}}" method="post">
{{#hiddeninputs}}
<input type="hidden" name="{{name}}" value="{{value}}">
{{/hiddeninputs}}
<div class="form-row">
<div class="col">
<div class="form-check">
<input class="form-check-input" type="radio" name="regradeselectedattempts"
id="regradeallattempts" value="0" {{^hasselectedattempts}}checked{{/hasselectedattempts}}>
<label class="form-check-label" for="regradeallattempts">
{{# str }} regrade_allattempts, quiz_overview {{/str}}
</label>
</div>
</div>
<div class="col">
<div class="form-check">
<input class="form-check-input" type="radio" name="regradeselectedattempts"
id="regradeselectedattempts" value="1" {{#hasselectedattempts}}checked{{/hasselectedattempts}}
{{^hasselectedattempts}}disabled{{/hasselectedattempts}}>
<label class="form-check-label" for="regradeselectedattempts">
{{# str }} regrade_selectedattempts, quiz_overview {{/str}}
</label>
</div>
</div>
</div>
<div class="form-row mt-2">
<div class="col">
<div class="form-check">
<input class="form-check-input" type="radio" name="regradeselectedquestions"
id="regradeallquestions" value="0" checked>
<label class="form-check-label" for="regradeallquestions">
{{# str }} regrade_allquestions, quiz_overview {{/str}}
</label>
</div>
</div>
<div class="col">
<div class="form-check">
<input class="form-check-input" type="radio" name="regradeselectedquestions"
id="regradeselectedquestions" value="1">
<label class="form-check-label" for="regradeselectedquestions">
{{# str }} regrade_selectedquestions, quiz_overview {{/str}}
</label>
</div>
<fieldset>
{{#questions}}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="regradeslot[]"
id="regradeslot{{slot}}" value="{{slot}}" disabled>
<label class="form-check-label" for="regradeslot{{slot}}">
{{name}}
</label>
</div>
{{/questions}}
</fieldset>
</div>
</div>
<div class="modal-footer mt-2 pb-0" data-region="footer">
<input type="submit" class="btn btn-primary" id="regradebutton" name="regrade" value="{{#str}} regrade_regradenow, quiz_overview {{/str}}">
<input type="submit" class="btn btn-primary" id="dryrunbutton" name="dryrunregrade" value="{{#str}} regrade_dryrun, quiz_overview {{/str}}">
</div>
</form>

View File

@ -101,8 +101,6 @@ Feature: Basic use of the Grades report
# Verify groups are displayed correctly.
And I set the field "Visible groups" to "English"
And "Full regrade for group 'English'" "button" should exist
And "Dry run a full regrade for group 'English'" "button" should exist
And I should see "Number of students in group 'English' achieving grade ranges"
@javascript

View File

@ -43,11 +43,11 @@ Feature: Regrading quiz attempts using the Grades report
| 1 | True |
| 2 | toad |
Scenario: Regrade all attempts
Scenario: Regrade all attempts and all questions.
Given I am on the "Quiz for testing regrading" "quiz activity" page logged in as teacher
And I navigate to "Results" in current page administration
When I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
# Note, the order is not defined, so we can only check part of the message.
# Also, nothing has changed in the quiz, so the regrade won't alter any scores,
# but this is still a useful test that the regrade process completes without errors.
@ -60,12 +60,12 @@ Feature: Regrading quiz attempts using the Grades report
And I should see "Quiz for testing regrading"
And I should see "Overall number of students achieving grade ranges"
Scenario: Regrade selected attempts
Scenario: Regrade selected attempts and all questions.
Given I am on the "Quiz for testing regrading" "quiz activity" page logged in as teacher
And I navigate to "Results" in current page administration
When I click on "Select attempt" "checkbox" in the "Student Two" "table_row"
And I press "Regrade selected attempts"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
Then I should see "Quiz for testing regrading"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
@ -75,7 +75,58 @@ Feature: Regrading quiz attempts using the Grades report
And I should see "Quiz for testing regrading"
And I should see "Overall number of students achieving grade ranges"
Scenario: Dry-run a full regrade, then regrade the attempts that will need it.
Scenario: Regrade all attempts and selected questions.
Given I am on the "Quiz for testing regrading" "mod_quiz > edit" page logged in as teacher
When I follow "Edit question SA"
And I set the field "id_fraction_1" to "50%"
And I press "id_submitbutton"
And I follow "Edit question TF"
And I set the field "Correct answer" to "False"
And I press "id_submitbutton"
And I follow "Attempts: 2"
And I press "Regrade attempts..."
And I click on "Selected questions" "radio"
And I click on "Question 1" "checkbox"
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
Then I should see "Quiz for testing regrading"
And I should see "Finished regrading (2/2)"
And I should see "Regrade completed"
And I press "Continue"
And "Student OneReview attempt" row "Q. 1/50.00Sort by Q. 1/50.00 Ascending" column of "attempts" table should contain "50.00/0.00"
And "Student TwoReview attempt" row "Q. 1/50.00Sort by Q. 1/50.00 Ascending" column of "attempts" table should contain "50.00/0.00"
And "Student OneReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "100.00/50.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/40.00"
Scenario: Regrade selected attempts and selected questions.
Given I am on the "Quiz for testing regrading" "mod_quiz > edit" page logged in as teacher
When I follow "Edit question SA"
And I set the field "id_fraction_1" to "50%"
And I press "id_submitbutton"
And I follow "Edit question TF"
And I set the field "Correct answer" to "False"
And I press "id_submitbutton"
And I follow "Attempts: 2"
And I click on "Select attempt" "checkbox" in the "Student Two" "table_row"
And I press "Regrade attempts..."
And the "Question 1" "checkbox" should be disabled
And the "Question 2" "checkbox" should be disabled
And I click on "Selected questions" "radio"
And the "Question 1" "checkbox" should be enabled
And the "Question 2" "checkbox" should be enabled
And the "Regrade now" "button" should be disabled
And the "Dry run" "button" should be disabled
And I click on "Question 1" "checkbox"
And the "Regrade now" "button" should be enabled
And the "Dry run" "button" should be enabled
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
Then I should see "Quiz for testing regrading"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
And I press "Continue"
And "Student TwoReview attempt" row "Q. 1/50.00Sort by Q. 1/50.00 Ascending" column of "attempts" table should contain "50.00/0.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/40.00"
Scenario: Dry-run all attempts, then regrade all attempts.
Given I am on the "Quiz for testing regrading" "mod_quiz > edit" page logged in as teacher
And I follow "Edit question SA"
And I set the field "id_fraction_1" to "50%"
@ -83,8 +134,8 @@ Feature: Regrading quiz attempts using the Grades report
And I set the field "version" in the "TF" "list_item" to "v1"
And I set the field "version" in the "SA" "list_item" to "v2 (latest)"
And I follow "Attempts: 2"
And I press "Dry run a full regrade"
And I press "Regrade attempts..."
And I click on "Dry run" "button" in the "Regrade" "dialogue"
# Note, the order is not defined, so we can only check part of the message.
Then I should see "Quiz for testing regrading"
And I should see "Finished regrading (2/2)"
@ -97,7 +148,42 @@ Feature: Regrading quiz attempts using the Grades report
# At this point, it is showing what would change.
And "Student TwoReview attempt" row "Q. 2/50.00Sort by Q. 2/50.00 Ascending" column of "attempts" table should contain "40.00/25.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/75.00"
And I press "Regrade attempts marked as needing regrading (1)"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
And I should see "Quiz for testing regrading"
And I should see "Finished regrading (2/2)"
And I should see "Regrade completed"
And I press "Continue"
# These next tests just serve to check we got back to the report.
And I should see "Quiz for testing regrading"
And I should see "Overall number of students achieving grade ranges"
# Now, both old-score strike-through and new score plain, are still shown, but now it indicates what did change.
And "Student TwoReview attempt" row "Q. 2/50.00Sort by Q. 2/50.00 Ascending" column of "attempts" table should contain "40.00/25.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/75.00"
Scenario: Dry-run a full regrade, then regrade the commit regrade.
Given I am on the "Quiz for testing regrading" "mod_quiz > edit" page logged in as teacher
When I follow "Edit question SA"
And I set the field "id_fraction_1" to "50%"
And I press "id_submitbutton"
And I set the field "version" in the "TF" "list_item" to "v1"
And I set the field "version" in the "SA" "list_item" to "v2 (latest)"
And I follow "Attempts: 2"
And I press "Regrade attempts..."
And I click on "Dry run" "button" in the "Regrade" "dialogue"
# Note, the order is not defined, so we can only check part of the message.
And I should see "Quiz for testing regrading"
And I should see "Finished regrading (2/2)"
And I should see "Regrade completed"
And I press "Continue"
And "Student One" row "Regrade" column of "attempts" table should not contain "Needed"
And "Student TwoReview attempt" row "Regrade" column of "attempts" table should contain "Needed"
# In the following, the first number is strike-through, and the second is not, but Behat can't see that.
# At this point, it is showing what would change.
And "Student TwoReview attempt" row "Q. 2/50.00Sort by Q. 2/50.00 Ascending" column of "attempts" table should contain "40.00/25.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/75.00"
Then I should see "The last dry-run of a regrade found that the re-grade would change the marks for 1 questions in 1 attempts."
And I press "Commit regrade"
And I should see "Quiz for testing regrading"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
@ -109,12 +195,13 @@ Feature: Regrading quiz attempts using the Grades report
# Now, both old-score strike-through and new score plain, are still shown, but now it indicates what did change.
And "Student TwoReview attempt" row "Q. 2/50.00Sort by Q. 2/50.00 Ascending" column of "attempts" table should contain "40.00/25.00"
And "Student TwoReview attempt" row "Grade/100.00Sort by Grade/100.00 Ascending" column of "attempts" table should contain "90.00/75.00"
And "Regrade attempts marked as needing regrading" "button" should not exist
And I should not see "The last dry-run of a regrade found that the re-grade would change the marks for 1 questions in 1 attempts."
Scenario: Regrade all attempts works against quiz selected question version
Given I am on the "Quiz for testing regrading" "quiz activity" page logged in as teacher
And I navigate to "Results" in current page administration
When I press "Dry run a full regrade"
And I press "Regrade attempts..."
And I click on "Dry run" "button" in the "Regrade" "dialogue"
Then I should see "Quiz for testing regrading"
And I should see "Finished regrading (2/2)"
And I should see "Regrade completed"
@ -129,12 +216,14 @@ Feature: Regrading quiz attempts using the Grades report
And I am on the "Quiz for testing regrading" "mod_quiz > edit" page
And I set the field "version" in the "TF" "list_item" to "v2 (latest)"
And I navigate to "Results" in current page administration
And I press "Dry run a full regrade"
And I press "Regrade attempts..."
And I click on "Dry run" "button" in the "Regrade" "dialogue"
And I should see "Regrade completed"
And I press "Continue"
And "student1@example.com" row "Regrade" column of "attempts" table should contain "Needed"
And "Correct" "icon" should appear before "50.00/0.00" "text"
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
And I should see "Regrade completed"
And I press "Continue"
Then "student1@example.com" row "Regrade" column of "attempts" table should contain "Done"
@ -173,7 +262,8 @@ Feature: Regrading quiz attempts using the Grades report
And I am on the "Quiz for testing regrading" "mod_quiz > edit" page
And I set the field "version" in the "TF" "list_item" to "Always latest"
And I navigate to "Results" in current page administration
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
And I press "Continue"
@ -212,7 +302,8 @@ Feature: Regrading quiz attempts using the Grades report
And I navigate to "Results" in current page administration
And "student3@example.com" row "Q. 1/100.00Sort by Q. 1/100.00 Ascending" column of "attempts" table should contain "100.00"
And "Correct" "icon" should be visible
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
And I press "Continue"

View File

@ -42,7 +42,8 @@ Feature: Quiz regrade when not possible
# Try a regrade, and verify what happened is reported.
When I am on the "Quiz for testing regrading" "mod_quiz > grades report" page
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
Then I should see "Quiz for testing regrading"
And I should see "The following questions could not be regraded in attempt 1 by Student One"

View File

@ -51,12 +51,14 @@ Feature: Allow students to redo questions in a practice quiz, without starting a
And I click on "Check" "button" in the "First question" "question"
And I press "Try another question like this one"
And I am on the "Quiz 1" "mod_quiz > Grades report" page logged in as "teacher"
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
Then I should see "Finished regrading (1/1)"
And I should see "Regrade completed"
And I press "Continue"
# Regrade a second time, to ensure the first regrade did not corrupt any data.
And I press "Regrade all"
And I press "Regrade attempts..."
And I click on "Regrade now" "button" in the "Regrade" "dialogue"
And I should see "Finished regrading (1/1)"
And I should see "Regrade completed"