forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'MDL-79254_master' of https://github.com/marxjohnson/moodle
- Loading branch information
Showing
11 changed files
with
367 additions
and
100 deletions.
There are no files selected for viewing
47 changes: 47 additions & 0 deletions
47
mod/quiz/report/statistics/classes/event/observer/attempt_submitted.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
// 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/>. | ||
|
||
namespace quiz_statistics\event\observer; | ||
|
||
use quiz_statistics\task\recalculate; | ||
|
||
/** | ||
* Event observer for \mod_quiz\event\attempt_submitted | ||
* | ||
* @package quiz_statistics | ||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net} | ||
* @author Mark Johnson <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class attempt_submitted { | ||
/** | ||
* Queue an ad-hoc task to recalculate statistics for the quiz. | ||
* | ||
* This will defer running the task for 1 hour, to give other attempts in progress | ||
* a chance to submit. | ||
* | ||
* @param \mod_quiz\event\attempt_submitted $event | ||
* @return void | ||
*/ | ||
public static function process(\mod_quiz\event\attempt_submitted $event): void { | ||
$data = $event->get_data(); | ||
$quizid = $data['other']['quizid']; | ||
|
||
$task = recalculate::instance($quizid); | ||
$task->set_next_run_time(time() + HOURSECS); | ||
\core\task\manager::queue_adhoc_task($task, true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,6 @@ | |
|
||
use core\dml\sql_join; | ||
use mod_quiz\quiz_attempt; | ||
use mod_quiz\quiz_settings; | ||
use quiz_statistics_report; | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
@@ -36,88 +35,63 @@ | |
* @author Nathan Nguyen <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class recalculate extends \core\task\scheduled_task { | ||
/** @var int the maximum length of time one instance of this task will run. */ | ||
const TIME_LIMIT = 3600; | ||
class recalculate extends \core\task\adhoc_task { | ||
/** | ||
* Create a new instance of the task. | ||
* | ||
* This sets the properties so that only one task will be queued at a time for a given quiz. | ||
* | ||
* @param int $quizid | ||
* @return recalculate | ||
*/ | ||
public static function instance(int $quizid): recalculate { | ||
$task = new self(); | ||
$task->set_component('quiz_statistics'); | ||
$task->set_userid(get_admin()->id); | ||
$task->set_custom_data((object)[ | ||
'quizid' => $quizid, | ||
]); | ||
return $task; | ||
} | ||
|
||
|
||
public function get_name(): string { | ||
return get_string('recalculatetask', 'quiz_statistics'); | ||
} | ||
|
||
public function execute(): void { | ||
global $DB; | ||
$stoptime = time() + self::TIME_LIMIT; | ||
$dateformat = get_string('strftimedatetimeshortaccurate', 'core_langconfig'); | ||
$data = $this->get_custom_data(); | ||
$quiz = $DB->get_record('quiz', ['id' => $data->quizid]); | ||
if (!$quiz) { | ||
mtrace('Could not find quiz with ID ' . $data->quizid . '.'); | ||
return; | ||
} | ||
$course = $DB->get_record('course', ['id' => $quiz->course]); | ||
if (!$course) { | ||
mtrace('Could not find course with ID ' . $quiz->course . '.'); | ||
return; | ||
} | ||
$attemptcount = $DB->count_records('quiz_attempts', ['quiz' => $data->quizid, 'state' => quiz_attempt::FINISHED]); | ||
if ($attemptcount === 0) { | ||
mtrace('Could not find any finished attempts for course with ID ' . $data->quizid . '.'); | ||
return; | ||
} | ||
|
||
// TODO: MDL-75197, add quizid in quiz_statistics so that it is simpler to find quizzes for stats calculation. | ||
// Only calculate stats for quizzes which have recently finished attempt. | ||
$latestattempts = $DB->get_records_sql(" | ||
SELECT q.id AS quizid, | ||
q.name AS quizname, | ||
q.grademethod AS quizgrademethod, | ||
c.id AS courseid, | ||
c.shortname AS courseshortname, | ||
MAX(quiza.timefinish) AS mostrecentattempttime, | ||
COUNT(1) AS numberofattempts | ||
FROM {quiz_attempts} quiza | ||
JOIN {quiz} q ON q.id = quiza.quiz | ||
JOIN {course} c ON c.id = q.course | ||
WHERE quiza.preview = 0 | ||
AND quiza.state = :quizstatefinished | ||
GROUP BY q.id, q.name, q.grademethod, c.id, c.shortname | ||
ORDER BY MAX(quiza.timefinish) DESC | ||
", ["quizstatefinished" => quiz_attempt::FINISHED]); | ||
|
||
$anyexception = null; | ||
foreach ($latestattempts as $latestattempt) { | ||
if (time() >= $stoptime) { | ||
mtrace("This task has been running for more than " . | ||
format_time(self::TIME_LIMIT) . " so stopping this execution."); | ||
break; | ||
} | ||
|
||
// Check if there is any existing question stats, and it has been calculated after latest quiz attempt. | ||
$qubaids = quiz_statistics_qubaids_condition($latestattempt->quizid, | ||
new sql_join(), $latestattempt->quizgrademethod); | ||
$lateststatstime = $DB->get_field('quiz_statistics', 'COALESCE(MAX(timemodified), 0)', | ||
['hashcode' => $qubaids->get_hash_code()]); | ||
|
||
$quizinfo = "'$latestattempt->quizname' ($latestattempt->quizid) in course " . | ||
"$latestattempt->courseshortname ($latestattempt->courseid) has most recent attempt finished at " . | ||
userdate($latestattempt->mostrecentattempttime, $dateformat); | ||
if ($lateststatstime) { | ||
$quizinfo .= " and statistics from " . userdate($lateststatstime, $dateformat); | ||
} | ||
|
||
if ($lateststatstime >= $latestattempt->mostrecentattempttime) { | ||
mtrace(" " . $quizinfo . " so nothing to do."); | ||
continue; | ||
} | ||
|
||
// OK, so we need to calculate for this quiz. | ||
mtrace(" " . $quizinfo . " so re-calculating statistics for $latestattempt->numberofattempts attempts, start time " . | ||
userdate(time(), $dateformat) . " ..."); | ||
|
||
try { | ||
$quizobj = quiz_settings::create($latestattempt->quizid); | ||
$report = new quiz_statistics_report(); | ||
$report->clear_cached_data($qubaids); | ||
$report->calculate_questions_stats_for_question_bank($quizobj->get_quizid()); | ||
mtrace(" Calculations completed at " . userdate(time(), $dateformat) . "."); | ||
mtrace("Re-calculating statistics for quiz {$quiz->name} ({$quiz->id}) " . | ||
"from course {$course->shortname} ({$course->id}) with {$attemptcount} attempts, start time " . | ||
userdate(time(), $dateformat) . " ..."); | ||
|
||
} catch (\Throwable $e) { | ||
// We don't want an exception from one quiz to stop processing of other quizzes. | ||
mtrace_exception($e); | ||
$anyexception = $e; | ||
} | ||
} | ||
$qubaids = quiz_statistics_qubaids_condition( | ||
$quiz->id, | ||
new sql_join(), | ||
$quiz->grademethod | ||
); | ||
|
||
if ($anyexception) { | ||
// If there was any error, ensure the task fails. | ||
throw $anyexception; | ||
} | ||
$report = new quiz_statistics_report(); | ||
$report->clear_cached_data($qubaids); | ||
$report->calculate_questions_stats_for_question_bank($quiz->id); | ||
mtrace(' Calculations completed at ' . userdate(time(), $dateformat) . '.'); | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
mod/quiz/report/statistics/classes/tests/statistics_helper.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
// 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/>. | ||
namespace quiz_statistics\tests; | ||
|
||
/** | ||
* Test helper functions for statistics | ||
* | ||
* @package quiz_statistics | ||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net} | ||
* @author Mark Johnson <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class statistics_helper { | ||
/** | ||
* Run any ad-hoc recalculation tasks that have been scheduled. | ||
* | ||
* We need a special function to do this as the tasks are deferred by one hour, | ||
* so we need to pass a custom $timestart argument. | ||
* | ||
* @return void | ||
*/ | ||
public static function run_pending_recalculation_tasks(): void { | ||
while ($task = \core\task\manager::get_next_adhoc_task( | ||
time() + HOURSECS + 1, | ||
false, | ||
'\quiz_statistics\task\recalculate' | ||
)) { | ||
$task->execute(); | ||
\core\task\manager::adhoc_task_complete($task); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,25 +15,19 @@ | |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* Legacy Cron Quiz Reports Task | ||
* | ||
* @package quiz_statistics | ||
* @copyright 2017 Michael Hughes, University of Strathclyde | ||
* @author Michael Hughes <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
* Add event observers for quiz_statistics | ||
* | ||
* @package quiz_statistics | ||
* @copyright 2023 onwards Catalyst IT EU {@link https://catalyst-eu.net} | ||
* @author Mark Johnson <[email protected]> | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
||
$tasks = [ | ||
$observers = [ | ||
[ | ||
'classname' => 'quiz_statistics\task\recalculate', | ||
'blocking' => 0, | ||
'minute' => 'R', | ||
'hour' => '*/4', | ||
'day' => '*', | ||
'dayofweek' => '*', | ||
'month' => '*' | ||
] | ||
'eventname' => '\mod_quiz\event\attempt_submitted', | ||
'callback' => '\quiz_statistics\event\observer\attempt_submitted::process', | ||
], | ||
]; |
Oops, something went wrong.