Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
declare(strict_types=1);
18
 
19
namespace core_course\reportbuilder\datasource;
20
 
21
use completion_completion;
22
use completion_criteria_self;
23
use core_reportbuilder\local\filters\boolean_select;
24
use core_reportbuilder\local\filters\date;
25
use core_reportbuilder\local\filters\duration;
26
use core_reportbuilder\local\filters\select;
27
use core_reportbuilder\local\filters\text;
28
use core_reportbuilder_generator;
29
use core_reportbuilder_testcase;
30
use core_user;
31
use grade_item;
32
 
33
defined('MOODLE_INTERNAL') || die();
34
 
35
global $CFG;
36
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
37
 
38
/**
39
 * Course participants datasource tests
40
 *
41
 * @package     core_course
42
 * @covers      \core_course\reportbuilder\datasource\participants
43
 * @copyright   2022 David Matamoros <davidmc@moodle.com>
44
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class participants_test extends core_reportbuilder_testcase {
47
 
48
    /**
49
     * Load required test libraries
50
     */
51
    public static function setUpBeforeClass(): void {
52
        global $CFG;
53
 
54
        require_once("{$CFG->libdir}/gradelib.php");
55
        require_once("{$CFG->dirroot}/completion/criteria/completion_criteria_self.php");
56
    }
57
 
58
    /**
59
     * Test default datasource
60
     */
61
    public function test_datasource_default(): void {
62
        global $DB;
63
 
64
        $this->resetAfterTest();
65
 
66
        // Course one, two manually enrolled users.
67
        $courseone = $this->getDataGenerator()->create_course(['fullname' => 'Zebras']);
68
        $userone = $this->getDataGenerator()->create_and_enrol($courseone, 'student', ['firstname' => 'Zoe']);
69
        $usertwo = $this->getDataGenerator()->create_and_enrol($courseone, 'student', ['firstname' => 'Amy']);
70
 
71
        // Course two, two self enrolled users (one inactive).
72
        $coursetwo = $this->getDataGenerator()->create_course(['fullname' => 'Aardvarks']);
73
 
74
        $enrol = $DB->get_record('enrol', ['courseid' => $coursetwo->id, 'enrol' => 'self']);
75
        enrol_get_plugin($enrol->enrol)->update_status($enrol, ENROL_INSTANCE_ENABLED);
76
 
77
        $this->getDataGenerator()->enrol_user($userone->id, $coursetwo->id, null, 'self');
78
        $this->getDataGenerator()->enrol_user($usertwo->id, $coursetwo->id, null, 'self', 0, 0, ENROL_USER_SUSPENDED);
79
 
80
        /** @var core_reportbuilder_generator $generator */
81
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
82
        $report = $generator->create_report(['name' => 'Participants', 'source' => participants::class, 'default' => 1]);
83
 
84
        $content = $this->get_custom_report_content($report->get('id'));
85
 
86
        // Default columns are course, user, method. Sorted by each.
87
        $courseoneurl = course_get_url($courseone);
88
        $coursetwourl = course_get_url($coursetwo);
89
 
90
        $useroneurl = core_user::get_profile_url($userone);
91
        $usertwourl = core_user::get_profile_url($usertwo);
92
 
93
        $this->assertEquals([
94
            ["<a href=\"{$coursetwourl}\">{$coursetwo->fullname}</a>",
95
                "<a href=\"{$useroneurl}\">" . fullname($userone) . "</a>", 'Self enrolment (Student)'],
96
            ["<a href=\"{$courseoneurl}\">{$courseone->fullname}</a>",
97
                "<a href=\"{$usertwourl}\">" . fullname($usertwo) . "</a>", 'Manual enrolments'],
98
            ["<a href=\"{$courseoneurl}\">{$courseone->fullname}</a>",
99
                "<a href=\"{$useroneurl}\">" . fullname($userone) . "</a>", 'Manual enrolments'],
100
        ], array_map('array_values', $content));
101
    }
102
 
103
    /**
104
     * Test datasource columns that aren't added by default
105
     */
106
    public function test_datasource_non_default_columns(): void {
107
        global $DB;
108
        $this->resetAfterTest();
109
 
110
        $timestart = time() - DAYSECS;
111
        $timeend = $timestart + 3 * DAYSECS;
112
        $timecompleted = $timestart + 2 * DAYSECS;
113
        $timelastaccess = time() + 4 * DAYSECS;
114
 
115
        $category = $this->getDataGenerator()->create_category(['name' => 'Music']);
116
        $course = $this->getDataGenerator()->create_course([
117
            'category' => $category->id,
118
            'fullname' => 'All about Lionel at the work place',
119
            'enablecompletion' => true,
120
            'startdate' => $timestart,
121
            'enddate' => $timeend,
122
        ]);
123
 
124
        $user1 = self::getDataGenerator()->create_user();
125
        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student',
126
            'manual', $timestart, $timeend, ENROL_USER_ACTIVE);
127
 
128
        // Add them to a group.
129
        $group = self::getDataGenerator()->create_group(['courseid' => $course->id]);
130
        self::getDataGenerator()->create_group_member(['groupid' => $group->id, 'userid' => $user1->id]);
131
 
132
        // Create self completion, mark as complete for the user.
133
        $criteriaconfig = (object) ['id' => $course->id, 'criteria_self' => true];
134
        (new completion_criteria_self())->update_config($criteriaconfig);
135
 
136
        $ccompletion = new completion_completion(['course' => $course->id, 'userid' => $user1->id]);
137
        $ccompletion->mark_enrolled($timestart);
138
        $ccompletion->mark_complete($timecompleted);
139
 
140
        // Update final grade for the user.
141
        $courseitem = grade_item::fetch_course_item($course->id);
142
        $courseitem->update_final_grade($user1->id, 42.5);
143
 
144
        // Set some last access value for the user in the course.
145
        $DB->insert_record('user_lastaccess',
146
            ['userid' => $user1->id, 'courseid' => $course->id, 'timeaccess' => $timelastaccess]);
147
 
148
        /** @var core_reportbuilder_generator $generator */
149
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
150
        $report = $generator->create_report(['name' => 'Participants', 'source' => participants::class, 'default' => false]);
151
 
152
        $generator->create_column(['reportid' => $report->get('id'),
153
            'uniqueidentifier' => 'course:fullname']);
154
        $generator->create_column(['reportid' => $report->get('id'),
155
            'uniqueidentifier' => 'course_category:name']);
156
        $generator->create_column(['reportid' => $report->get('id'),
157
            'uniqueidentifier' => 'user:fullname']);
158
 
159
        // Enrol entity (report ordering by enrolment name).
160
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:name', 'sortenabled' => 1]);
161
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:plugin']);
162
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:enabled']);
163
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:period']);
164
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:startdate']);
165
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:enddate']);
166
 
167
        // Role entity.
168
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:name']);
169
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:shortname']);
170
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'role:description']);
171
 
172
        $generator->create_column(['reportid' => $report->get('id'),
173
            'uniqueidentifier' => 'group:name']);
174
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'completion:criteria']);
175
        $generator->create_column(['reportid' => $report->get('id'),
176
            'uniqueidentifier' => 'completion:completed']);
177
        $generator->create_column(['reportid' => $report->get('id'),
178
            'uniqueidentifier' => 'access:timeaccess']);
179
        $generator->create_column(['reportid' => $report->get('id'),
180
            'uniqueidentifier' => 'completion:progresspercent']);
181
        $generator->create_column(['reportid' => $report->get('id'),
182
            'uniqueidentifier' => 'completion:timeenrolled']);
183
        $generator->create_column(['reportid' => $report->get('id'),
184
            'uniqueidentifier' => 'completion:timestarted']);
185
        $generator->create_column(['reportid' => $report->get('id'),
186
            'uniqueidentifier' => 'completion:timecompleted']);
187
        $generator->create_column(['reportid' => $report->get('id'),
188
            'uniqueidentifier' => 'completion:reaggregate']);
189
        $generator->create_column(['reportid' => $report->get('id'),
190
            'uniqueidentifier' => 'completion:dayscourse']);
191
        $generator->create_column(['reportid' => $report->get('id'),
192
            'uniqueidentifier' => 'completion:daysuntilcompletion']);
193
        $generator->create_column(['reportid' => $report->get('id'),
194
            'uniqueidentifier' => 'completion:grade']);
195
 
196
        // Add filter to the report.
197
        $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:plugin']);
198
 
199
        $content = $this->get_custom_report_content($report->get('id'));
200
 
201
        // It should get 3 records (manual enrolment, self and guest).
202
        $this->assertCount(3, $content);
203
 
204
        // Filter by Manual enrolment method.
205
        $content = $this->get_custom_report_content($report->get('id'), 30, [
206
            'enrol:plugin_operator' => select::EQUAL_TO,
207
            'enrol:plugin_value' => 'manual',
208
        ]);
209
 
210
        $this->assertCount(1, $content);
211
 
212
        $this->assertEquals([
213
            'All about Lionel at the work place', // Course name.
214
            'Music', // Course category name.
215
            fullname($user1), // User fullname.
216
            'Manual enrolments', // Enrolment method.
217
            'Manual enrolments', // Enrolment plugin.
218
            'Yes', // Enrolment enabled.
219
            '', // Enrolment period.
220
            '', // Enrolment start date.
221
            '', // Enrolment end date.
222
            'Student', // Role name.
223
            'student', // Role shortname.
224
            'Students generally have fewer privileges within a course.', // Role description.
225
            $group->name, // Group name.
226
            "All criteria below are required<ul>\n<li>Self completion: Self completion</li>\n</ul>", // Completion criteria.
227
            'Yes', // Course completed.
228
            userdate($timelastaccess), // Time last access.
229
            '100.0%', // Progress percentage.
230
            userdate($timestart), // Time enrolled.
231
            '', // Time started.
232
            userdate($timecompleted), // Time completed.
233
            '', // Reagreggate.
234
            2, // Days taking course.
235
            2, // Days until completion.
236
            '42.50', // Grade.
237
        ], array_values($content[0]));
238
    }
239
 
11 efrain 240
 
1 efrain 241
    /**
11 efrain 242
     * Test creating participants report, with aggregated last access date (minimum and maximum)
243
     */
244
    public function test_course_last_access_aggregation(): void {
245
        $this->resetAfterTest();
246
 
247
        $course = $this->getDataGenerator()->create_course();
248
 
249
        $userone = $this->getDataGenerator()->create_and_enrol($course);
250
        $useronelastaccess = $this->getDataGenerator()->create_user_course_lastaccess($userone, $course, 1622502000);
251
 
252
        $usertwo = $this->getDataGenerator()->create_and_enrol($course);
253
        $usertwolastaccess = $this->getDataGenerator()->create_user_course_lastaccess($usertwo, $course, 1622847600);
254
 
255
        /** @var core_reportbuilder_generator $generator */
256
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
257
 
258
        $report = $generator->create_report(['name' => 'Participants', 'source' => participants::class, 'default' => 0]);
259
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
260
        $column = $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'access:timeaccess']);
261
 
262
        // Course aggregated with "Minimum" last access.
263
        $column->set('aggregation', 'min')->update();
264
        $content = $this->get_custom_report_content($report->get('id'));
265
        $this->assertEquals([
266
            [$course->fullname, userdate($useronelastaccess->timeaccess)],
267
        ], array_map('array_values', $content));
268
 
269
        // Course aggregated with "Maximum" last access.
270
        $column->set('aggregation', 'max')->update();
271
        $content = $this->get_custom_report_content($report->get('id'));
272
        $this->assertEquals([
273
            [$course->fullname, userdate($usertwolastaccess->timeaccess)],
274
        ], array_map('array_values', $content));
275
    }
276
 
277
    /**
278
     * Test creating participants report, with aggregated days taking course column
279
     */
280
    public function test_completion_days_taking_course_aggregation(): void {
281
        $this->resetAfterTest();
282
 
283
        $courseone = $this->getDataGenerator()->create_course(['fullname' => 'Course 1', 'startdate' => 1622502000]);
284
        $coursetwo = $this->getDataGenerator()->create_course(['fullname' => 'Course 2']);
285
 
286
        // User one completed the course in two days.
287
        $userone = $this->getDataGenerator()->create_and_enrol($courseone);
288
        $completion = new completion_completion(['course' => $courseone->id, 'userid' => $userone->id]);
289
        $completion->mark_complete(1622502000 + (2 * DAYSECS));
290
 
291
        // User two completed the course in three days (lazy bum).
292
        $usertwo = $this->getDataGenerator()->create_and_enrol($courseone);
293
        $completion = new completion_completion(['course' => $courseone->id, 'userid' => $usertwo->id]);
294
        $completion->mark_complete(1622502000 + (3 * DAYSECS));
295
 
296
        /** @var core_reportbuilder_generator $generator */
297
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
298
 
299
        $report = $generator->create_report(['name' => 'Participants', 'source' => participants::class, 'default' => 0]);
300
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname', 'sortenabled' => 1]);
301
        $generator->create_column([
302
            'reportid' => $report->get('id'),
303
            'uniqueidentifier' => 'completion:dayscourse',
304
            'aggregation' => 'avg',
305
        ]);
306
 
307
        $content = $this->get_custom_report_content($report->get('id'));
308
        $this->assertEquals([
309
            [$courseone->fullname, '2.5'],
310
            [$coursetwo->fullname, ''],
311
        ], array_map('array_values', $content));
312
    }
313
 
314
    /**
1 efrain 315
     * Data provider for {@see test_datasource_filters}
316
     *
317
     * @return array
318
     */
319
    public function datasource_filters_provider(): array {
320
        global $DB;
321
 
322
        return [
323
            [
324
                'enrolment:status',
325
                [
326
                    'enrolment:status_operator' => select::EQUAL_TO,
327
                    'enrolment:status_value' => 1,
328
                ],
329
                ['Luna'],
330
            ],
331
            [
332
                'enrolment:timecreated',
333
                [
334
                    'enrolment:timecreated_operator' => date::DATE_CURRENT,
335
                    'enrolment:timecreated_unit' => date::DATE_UNIT_DAY,
336
                ],
337
                ['Kira'],
338
            ],
339
            [
340
                'enrolment:timestarted',
341
                [
342
                    'enrolment:timestarted_operator' => date::DATE_CURRENT,
343
                    'enrolment:timecreated_unit' => date::DATE_UNIT_DAY,
344
                ],
345
                ['Luna'],
346
            ],
347
            [
348
                'enrolment:timeended',
349
                [
350
                    'enrolment:timeended_operator' => date::DATE_CURRENT,
351
                    'enrolment:timeended_unit' => date::DATE_UNIT_DAY,
352
                ],
353
                ['Luna'],
354
            ],
355
            [
356
                'enrol:enabled',
357
                [
358
                    'completion:enabled_operator' => boolean_select::CHECKED,
359
                ],
360
                ['Lionel', 'Kira', 'Luna'],
361
            ],
362
            [
363
                'enrol:period',
364
                [
365
                    'enrol:period_operator' => duration::DURATION_MAXIMUM,
366
                    'enrol:period_unit' => MINSECS,
367
                    'enrol:period_value' => 2,
368
                ],
369
                ['Lionel', 'Kira', 'Luna'],
370
            ],
371
            [
372
                'enrol:startdate',
373
                [
374
                    'enrol:startdate_operator' => date::DATE_EMPTY,
375
                ],
376
                ['Lionel', 'Kira', 'Luna'],
377
            ],
378
            [
379
                'enrol:enddate',
380
                [
381
                    'enrol:enddate_operator' => date::DATE_EMPTY,
382
                ],
383
                ['Lionel', 'Kira', 'Luna'],
384
            ],
385
            [
386
                'enrol:customname',
387
                [
388
                    'enrol:customname_operator' => text::IS_EMPTY,
389
                ],
390
                ['Luna', 'Kira', 'Lionel'],
391
            ],
392
            [
393
                'enrol:customname',
394
                [
395
                    'enrol:customname_operator' => text::IS_EQUAL_TO,
396
                    'enrol:customname_value' => 'All night long'
397
                ],
398
                [],
399
            ],
400
            [
401
                'role:name',
402
                [
403
                    'role:name_operator' => select::EQUAL_TO,
404
                    'role:name_value' => $DB->get_field('role', 'id', ['shortname' => 'editingteacher']),
405
                ],
406
                ['Luna'],
407
            ],
408
            [
409
                'completion:completed',
410
                [
411
                    'completion:completed_operator' => boolean_select::CHECKED,
412
                ],
413
                ['Lionel'],
414
            ],
415
            [
416
                'completion:timecompleted',
417
                [
418
                    'completion:timecompleted_operator' => date::DATE_NOT_EMPTY,
419
                ],
420
                ['Lionel'],
421
            ],
422
            [
423
                'completion:timeenrolled',
424
                [
425
                    'completion:timeenrolled_operator' => date::DATE_NOT_EMPTY,
426
                ],
427
                ['Lionel'],
428
            ],
429
            [
430
                'completion:timestarted',
431
                [
432
                    'completion:timestarted_operator' => date::DATE_NOT_EMPTY,
433
                ],
434
                ['Lionel'],
435
            ],
436
            [
437
                'completion:reaggregate',
438
                [
439
                    'completion:reaggregate_operator' => date::DATE_NOT_EMPTY,
440
                ],
441
                ['Lionel'],
442
            ],
443
        ];
444
    }
445
 
446
    /**
447
     * Test getting filter SQL
448
     *
449
     * @param string $filter
450
     * @param array $filtervalues
451
     * @param string[] $expected
452
     *
453
     * @dataProvider datasource_filters_provider
454
     */
455
    public function test_datasource_filters(string $filter, array $filtervalues, array $expected): void {
456
        global $DB;
457
        $this->resetAfterTest();
458
 
459
        $timestart = time() - DAYSECS;
460
        $timeend = $timestart + 3 * DAYSECS;
461
        $timecompleted = $timestart + 2 * DAYSECS;
462
        $timelastaccess = time() + 4 * DAYSECS;
463
 
464
        $category = $this->getDataGenerator()->create_category(['name' => 'Music']);
465
        $course = $this->getDataGenerator()->create_course([
466
            'category' => $category->id,
467
            'fullname' => 'All about Lionel at the work place',
468
            'enablecompletion' => true,
469
            'startdate' => $timestart,
470
            'enddate' => $timeend,
471
        ]);
472
 
473
        $user1 = self::getDataGenerator()->create_user(['firstname' => 'Lionel']);
474
        $user2 = self::getDataGenerator()->create_user(['firstname' => 'Kira']);
475
        $user3 = self::getDataGenerator()->create_user(['firstname' => 'Luna']);
476
 
477
        $this->getDataGenerator()->enrol_user($user1->id, $course->id, 'student',
478
            'manual', $timestart - 8 * DAYSECS, $timeend, ENROL_USER_ACTIVE);
479
        $this->getDataGenerator()->enrol_user($user2->id, $course->id, 'student',
480
            'manual', $timestart, $timeend, ENROL_USER_ACTIVE);
481
        $this->getDataGenerator()->enrol_user($user3->id, $course->id, 'editingteacher',
482
            'manual', time(), time(), ENROL_USER_SUSPENDED);
483
 
484
        // Mark course as completed for the user.
485
        $ccompletion = new completion_completion(array('course' => $course->id, 'userid' => $user1->id));
486
        $ccompletion->mark_enrolled($timestart);
487
        $ccompletion->mark_inprogress($timestart);
488
        $ccompletion->mark_complete($timecompleted);
489
 
490
        // Set some last access value for the user in the course.
491
        $DB->insert_record('user_lastaccess',
492
            ['userid' => $user1->id, 'courseid' => $course->id, 'timeaccess' => $timelastaccess]);
493
 
494
        /** @var core_reportbuilder_generator $generator */
495
        $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
496
        $report = $generator->create_report(['name' => 'Participants', 'source' => participants::class, 'default' => false]);
497
 
498
        // Add user firstname column to the report.
499
        $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname']);
500
 
501
        $DB->set_field('user_enrolments', 'timecreated', 0, ['userid' => $user1->id]);
502
        $DB->set_field('user_enrolments', 'timecreated', 0, ['userid' => $user3->id]);
503
 
504
        // Add filters to the report.
505
        $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => 'enrol:plugin']);
506
        $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filter]);
507
 
508
        // Apply filters.
509
        $filtermanual = ['enrol:plugin_operator' => select::EQUAL_TO, 'enrol:plugin_value' => 'manual'];
510
        $content = $this->get_custom_report_content($report->get('id'), 30, $filtermanual + $filtervalues);
511
 
512
        $this->assertEqualsCanonicalizing($expected, array_column($content, 'c0_firstname'));
513
    }
514
 
515
    /**
516
     * Stress test datasource
517
     *
518
     * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
519
     */
520
    public function test_stress_datasource(): void {
521
        if (!PHPUNIT_LONGTEST) {
522
            $this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
523
        }
524
 
525
        $this->resetAfterTest();
526
 
527
        $course = $this->getDataGenerator()->create_course();
528
        $this->getDataGenerator()->create_and_enrol($course);
529
 
530
        $this->datasource_stress_test_columns(participants::class);
531
        $this->datasource_stress_test_columns_aggregation(participants::class);
532
        $this->datasource_stress_test_conditions(participants::class, 'course:idnumber');
533
    }
534
}