mirror of
https://github.com/moodle/moodle.git
synced 2025-01-16 21:18:33 +01:00
MDL-83917 core_completion: Create function count_modules_completed
The function returns the number of modules completed by a user and executes a COUNT aggregate function to avoid running many queries to obtain this information, aiming to optimize performance. Co-authored-by: Carlos Castillo <carlos.castillo@moodle.com>
This commit is contained in:
parent
7bf1f87415
commit
ea6d8984ba
7
.upgradenotes/MDL-83917-2024121004111313.yml
Normal file
7
.upgradenotes/MDL-83917-2024121004111313.yml
Normal file
@ -0,0 +1,7 @@
|
||||
issueNumber: MDL-83917
|
||||
notes:
|
||||
core_completion:
|
||||
- message: >-
|
||||
The method `count_modules_completed` now delegate the logic to count the
|
||||
completed modules to the DBMS improving the performance of the method.
|
||||
type: improved
|
@ -77,16 +77,8 @@ class progress {
|
||||
}
|
||||
|
||||
// Get the number of modules that have been completed.
|
||||
$completed = 0;
|
||||
foreach ($modules as $module) {
|
||||
$data = $completion->get_data($module, true, $userid);
|
||||
if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
|
||||
$completed += 0;
|
||||
} else {
|
||||
$completed += 1;
|
||||
};
|
||||
}
|
||||
$totalcompleted = $completion->count_modules_completed($userid);
|
||||
|
||||
return ($completed / $count) * 100;
|
||||
return ($totalcompleted / $count) * 100;
|
||||
}
|
||||
}
|
||||
|
@ -1641,6 +1641,27 @@ class completion_info {
|
||||
|
||||
return (array)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of modules completed by a user in one specific course.
|
||||
*
|
||||
* @param int $userid The User ID.
|
||||
* @return int Total number of modules completed by a user
|
||||
*/
|
||||
public function count_modules_completed(int $userid): int {
|
||||
global $DB;
|
||||
|
||||
$sql = "SELECT COUNT(1)
|
||||
FROM {course_modules} cm
|
||||
JOIN {course_modules_completion} cmc ON cm.id = cmc.coursemoduleid
|
||||
WHERE cm.course = :courseid
|
||||
AND cmc.userid = :userid
|
||||
AND (cmc.completionstate = " . COMPLETION_COMPLETE . "
|
||||
OR cmc.completionstate = " . COMPLETION_COMPLETE_PASS . ")";
|
||||
$params = ['courseid' => $this->course_id, 'userid' => $userid];
|
||||
|
||||
return $DB->count_records_sql($sql, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2086,6 +2086,155 @@ final class completionlib_test extends advanced_testcase {
|
||||
['course' => $this->course->id]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_count_modules_completed().
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public static function count_modules_completed_provider(): array {
|
||||
return [
|
||||
'Multiple users with two different modules but only one completed' => [
|
||||
'existinguser' => true,
|
||||
'totalusers' => 3,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_COMPLETE,
|
||||
],
|
||||
[
|
||||
'name' => 'choice',
|
||||
'completionstate' => COMPLETION_INCOMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 1,
|
||||
],
|
||||
'Multiple users with three different modules but only two completed' => [
|
||||
'existinguser' => true,
|
||||
'totalusers' => 4,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_COMPLETE,
|
||||
],
|
||||
[
|
||||
'name' => 'choice',
|
||||
'completionstate' => COMPLETION_INCOMPLETE,
|
||||
],
|
||||
[
|
||||
'name' => 'workshop',
|
||||
'completionstate' => COMPLETION_COMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 2,
|
||||
],
|
||||
'Multiple users with one completion each' => [
|
||||
'existinguser' => true,
|
||||
'totalusers' => 5,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_COMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 1,
|
||||
],
|
||||
'One user with one completion' => [
|
||||
'existinguser' => true,
|
||||
'totalusers' => 1,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_COMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 1,
|
||||
],
|
||||
'Multiple users without completion' => [
|
||||
'existinguser' => true,
|
||||
'totalusers' => 3,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_INCOMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 0,
|
||||
],
|
||||
'Non-existing user' => [
|
||||
'existinguser' => false,
|
||||
'totalusers' => 1,
|
||||
'modules' => [
|
||||
[
|
||||
'name' => 'assign',
|
||||
'completionstate' => COMPLETION_INCOMPLETE,
|
||||
],
|
||||
],
|
||||
'expectedcount' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for count_modules_completed().
|
||||
*
|
||||
* @dataProvider count_modules_completed_provider
|
||||
* @param bool $existinguser Whether the given user exists or not.
|
||||
* @param int $totalusers The amount of users to check completion.
|
||||
* @param array $modules The course modules with its completion state.
|
||||
* @param int $expectedcount Expected total of modules completed.
|
||||
* @covers ::count_modules_completed
|
||||
*/
|
||||
public function test_count_modules_completed(bool $existinguser, int $totalusers, array $modules,
|
||||
int $expectedcount): void {
|
||||
global $DB;
|
||||
|
||||
$this->setAdminUser();
|
||||
$this->setup_data();
|
||||
|
||||
// Loop through the provided modules array and set the id key based on the generated module.
|
||||
$modules = array_map(function(array $module): array {
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('mod_' . $module['name']);
|
||||
$modinstance = $generator->create_instance([
|
||||
'course' => $this->course->id,
|
||||
'completion' => COMPLETION_TRACKING_AUTOMATIC,
|
||||
'completionsubmit' => true,
|
||||
]);
|
||||
$cminstance = get_coursemodule_from_instance($module['name'], $modinstance->id);
|
||||
|
||||
$module['id'] = $cminstance->id;
|
||||
return $module;
|
||||
}, $modules);
|
||||
|
||||
$completion = new completion_info($this->course);
|
||||
|
||||
if ($existinguser) {
|
||||
// Create users, assign them to a course and define the completion record.
|
||||
for ($i = 0; $i < $totalusers; $i++) {
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $this->course->id);
|
||||
$users[] = $user;
|
||||
|
||||
foreach ($modules as $module) {
|
||||
$cmcompletionrecords[] = (object)[
|
||||
'coursemoduleid' => $module['id'],
|
||||
'userid' => $user->id,
|
||||
'completionstate' => $module['completionstate'],
|
||||
'timemodified' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$DB->insert_records('course_modules_completion', $cmcompletionrecords);
|
||||
|
||||
foreach ($users as $user) {
|
||||
$this->assertEquals($expectedcount, $completion->count_modules_completed($user->id));
|
||||
}
|
||||
} else {
|
||||
$nonexistinguserid = 123;
|
||||
$this->assertEquals($expectedcount, $completion->count_modules_completed($nonexistinguserid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class core_completionlib_fake_recordset implements Iterator {
|
||||
|
Loading…
x
Reference in New Issue
Block a user