MDL-48594 Report: More filtering options on Activity Completion Report

This commit is contained in:
Tien Nguyen 2021-04-02 12:06:18 +07:00
parent 7811d7ace4
commit 5ad8101553
6 changed files with 339 additions and 73 deletions

View file

@ -0,0 +1,70 @@
<?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 report_progress\local;
/**
* Helper for report progress.
*
* @package report_progress
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL Juv3 or later
*/
class helper {
/** The default number of results to be shown per page. */
const COMPLETION_REPORT_PAGE = 25;
/**
* Get activities information by the activity include and activity order option.
*
* @param \completion_info $completion Completion information of course.
* @param string $activityinclude Activity type for filtering.
* @param string $activityorder Activity sort option.
* @return array The available activity types and activities array after filtering and sorting.
* @throws \coding_exception
*/
public static function get_activities_to_show(\completion_info $completion, string $activityinclude,
string $activityorder): array {
// Get all activity types.
$activities = $completion->get_activities();
$availableactivitytypes = [];
foreach ($activities as $activity) {
$availableactivitytypes[$activity->modname] = $activity->get_module_type_name(true);
}
asort($availableactivitytypes);
$availableactivitytypes = ['all' => get_string('allactivitiesandresources', 'report_progress')] +
$availableactivitytypes;
// Filter activities by type.
if (!empty($activityinclude) && $activityinclude !== 'all') {
$activities = array_filter($activities, function($activity) use ($activityinclude) {
return $activity->modname === $activityinclude;
});
}
// The activities are sorted by activity order on course page by default.
if ($activityorder === 'alphabetical') {
usort($activities, function($a, $b) {
return strcmp($a->name, $b->name);
});
}
return [$availableactivitytypes, $activities];
}
}

View file

@ -0,0 +1,94 @@
<?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 report_progress\output;
use single_select;
use plugin_renderer_base;
use html_writer;
/**
* Renderer for report progress.
*
* @package report_progress
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL Juv3 or later
*/
class renderer extends plugin_renderer_base {
/**
* Render include activity single select box.
*
* @param \moodle_url $url The base url.
* @param array $activitytypes The activity type options.
* @param string $activityinclude The current selected option.
* @return string HTML
* @throws \coding_exception
*/
public function render_include_activity_select(\moodle_url $url, array $activitytypes,
string $activityinclude): string {
$includeurl = fullclone($url);
$includeurl->remove_params(['page', 'activityinclude']);
$activityincludeselect = new single_select(
$url, 'activityinclude',
$activitytypes, $activityinclude, null, 'include-activity-select-report'
);
$activityincludeselect->set_label(get_string('include', 'report_progress'));
return \html_writer::div($this->output->render($activityincludeselect),
'include-activity-selector d-inline-block ml-3' );
}
/**
* Render activity order single select box.
*
* @param \moodle_url $url The base url.
* @param string $activityorder The current selected option.
* @return string HTML
* @throws \coding_exception
*/
public function render_activity_order_select(\moodle_url $url, string $activityorder): string {
$activityorderurl = fullclone($url);
$activityorderurl->remove_params(['activityorder']);
$options = ['orderincourse' => get_string('orderincourse', 'report_progress'),
'alphabetical' => get_string('alphabetical', 'report_progress')];
$sorttable = new single_select(
$activityorderurl, 'activityorder',
$options, $activityorder, null, 'activity-order-select-report'
);
$sorttable->set_label(get_string('activityorder', 'report_progress'));
return \html_writer::div($this->output->render($sorttable),
'activity-order-selector include-activity-selector d-inline-block ml-3');
}
/**
* Render download buttons.
*
* @param \moodle_url $url The base url.
* @return string HTML
* @throws \coding_exception
*/
public function render_download_buttons(\moodle_url $url): string {
$downloadurl = fullclone($url);
$downloadurl->remove_params(['page']);
$downloadurl->param('format', 'csv');
$downloadhtml = html_writer::start_tag('ul', ['class' => 'progress-actions']);
$downloadhtml .= html_writer::tag('li', html_writer::link($downloadurl, get_string('csvdownload', 'completion')));
$downloadurl->param('format', 'excelcsv');
$downloadhtml .= html_writer::tag('li', html_writer::link($downloadurl, get_string('excelcsvdownload', 'completion')));
$downloadhtml .= html_writer::end_tag('ul');
return $downloadhtml;
}
}

View file

@ -26,7 +26,7 @@
require('../../config.php'); require('../../config.php');
require_once($CFG->libdir . '/completionlib.php'); require_once($CFG->libdir . '/completionlib.php');
define('COMPLETION_REPORT_PAGE', 25); use \report_progress\local\helper;
// Get course // Get course
$id = required_param('course',PARAM_INT); $id = required_param('course',PARAM_INT);
@ -45,11 +45,13 @@ $format = optional_param('format','',PARAM_ALPHA);
$excel = $format == 'excelcsv'; $excel = $format == 'excelcsv';
$csv = $format == 'csv' || $excel; $csv = $format == 'csv' || $excel;
// Paging // Paging, sorting and filtering.
$start = optional_param('start', 0, PARAM_INT); $page = optional_param('page', 0, PARAM_INT);
$sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS); $sifirst = optional_param('sifirst', 'all', PARAM_NOTAGS);
$silast = optional_param('silast', 'all', PARAM_NOTAGS); $silast = optional_param('silast', 'all', PARAM_NOTAGS);
$start = optional_param('start', 0, PARAM_INT); $groupid = optional_param('group', 0, PARAM_INT);
$activityinclude = optional_param('activityinclude', 'all', PARAM_TEXT);
$activityorder = optional_param('activityorder', 'orderincourse', PARAM_TEXT);
// Whether to show extra user identity information // Whether to show extra user identity information
// TODO Does not support custom user profile fields (MDL-70456). // TODO Does not support custom user profile fields (MDL-70456).
@ -72,8 +74,8 @@ if ($sort !== '') {
if ($format !== '') { if ($format !== '') {
$url->param('format', $format); $url->param('format', $format);
} }
if ($start !== 0) { if ($page !== 0) {
$url->param('start', $start); $url->param('page', $page);
} }
if ($sifirst !== 'all') { if ($sifirst !== 'all') {
$url->param('sifirst', $sifirst); $url->param('sifirst', $sifirst);
@ -81,6 +83,16 @@ if ($sifirst !== 'all') {
if ($silast !== 'all') { if ($silast !== 'all') {
$url->param('silast', $silast); $url->param('silast', $silast);
} }
if ($groupid !== 0) {
$url->param('group', $groupid);
}
if ($activityinclude !== '') {
$url->param('activityinclude', $activityinclude);
}
if ($activityorder !== '') {
$url->param('activityorder', $activityorder);
}
$PAGE->set_url($url); $PAGE->set_url($url);
$PAGE->set_pagelayout('report'); $PAGE->set_pagelayout('report');
@ -96,10 +108,10 @@ if ($group===0 && $course->groupmode==SEPARATEGROUPS) {
} }
// Get data on activities and progress of all users, and give error if we've // Get data on activities and progress of all users, and give error if we've
// nothing to display (no users or no activities) // nothing to display (no users or no activities).
$reportsurl = $CFG->wwwroot.'/course/report.php?id='.$course->id;
$completion = new completion_info($course); $completion = new completion_info($course);
$activities = $completion->get_activities(); list($activitytypes, $activities) = helper::get_activities_to_show($completion, $activityinclude, $activityorder);
$output = $PAGE->get_renderer('report_progress');
if ($sifirst !== 'all') { if ($sifirst !== 'all') {
set_user_preference('ifirst', $sifirst); set_user_preference('ifirst', $sifirst);
@ -149,8 +161,8 @@ if ($total) {
$where_params, $where_params,
$group, $group,
$firstnamesort ? 'u.firstname ASC, u.lastname ASC' : 'u.lastname ASC, u.firstname ASC', $firstnamesort ? 'u.firstname ASC, u.lastname ASC' : 'u.lastname ASC, u.firstname ASC',
$csv ? 0 : COMPLETION_REPORT_PAGE, $csv ? 0 : helper::COMPLETION_REPORT_PAGE,
$csv ? 0 : $start, $csv ? 0 : $page * helper::COMPLETION_REPORT_PAGE,
$context $context
); );
} }
@ -182,8 +194,17 @@ if ($csv && $grandtotal && count($activities)>0) { // Only show CSV if there are
echo $OUTPUT->header(); echo $OUTPUT->header();
$PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]); $PAGE->requires->js_call_amd('report_progress/completion_override', 'init', [fullname($USER)]);
// Handle groups (if enabled) // Handle groups (if enabled).
groups_print_course_menu($course,$CFG->wwwroot.'/report/progress/?course='.$course->id); $groupurl = fullclone($url);
$groupurl->remove_params(['page', 'group']);
groups_print_course_menu($course, $groupurl);
// Display include activity filter.
echo $output->render_include_activity_select($url, $activitytypes, $activityinclude);
// Display activity order options.
echo $output->render_activity_order_select($url, $activityorder);
} }
if (count($activities)==0) { if (count($activities)==0) {
@ -213,57 +234,12 @@ $prefixfirst = 'sifirst';
$prefixlast = 'silast'; $prefixlast = 'silast';
// The URL used in the initials bar should reset the 'start' parameter. // The URL used in the initials bar should reset the 'start' parameter.
$initialsbarurl = new moodle_url($url); $initialsbarurl = fullclone($url);
$initialsbarurl->remove_params('start'); $initialsbarurl->remove_params('page');
$pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial', get_string('firstname'), $prefixfirst, $initialsbarurl); $pagingbar .= $OUTPUT->initials_bar($sifirst, 'firstinitial mt-2', get_string('firstname'), $prefixfirst, $initialsbarurl);
$pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $initialsbarurl); $pagingbar .= $OUTPUT->initials_bar($silast, 'lastinitial', get_string('lastname'), $prefixlast, $initialsbarurl);
$pagingbar .= $OUTPUT->paging_bar($total, $page, helper::COMPLETION_REPORT_PAGE, $url);
// Do we need a paging bar?
if ($total > COMPLETION_REPORT_PAGE) {
// Paging bar
$pagingbar .= '<div class="paging">';
$pagingbar .= get_string('page').': ';
$sistrings = array();
if ($sifirst != 'all') {
$sistrings[] = "sifirst={$sifirst}";
}
if ($silast != 'all') {
$sistrings[] = "silast={$silast}";
}
$sistring = !empty($sistrings) ? '&amp;'.implode('&amp;', $sistrings) : '';
// Display previous link
if ($start > 0) {
$pstart = max($start - COMPLETION_REPORT_PAGE, 0);
$pagingbar .= "(<a class=\"previous\" href=\"{$link}{$pstart}{$sistring}\">".get_string('previous').'</a>)&nbsp;';
}
// Create page links
$curstart = 0;
$curpage = 0;
while ($curstart < $total) {
$curpage++;
if ($curstart == $start) {
$pagingbar .= '&nbsp;'.$curpage.'&nbsp;';
} else {
$pagingbar .= "&nbsp;<a href=\"{$link}{$curstart}{$sistring}\">$curpage</a>&nbsp;";
}
$curstart += COMPLETION_REPORT_PAGE;
}
// Display next link
$nstart = $start + COMPLETION_REPORT_PAGE;
if ($nstart < $total) {
$pagingbar .= "&nbsp;(<a class=\"next\" href=\"{$link}{$nstart}{$sistring}\">".get_string('next').'</a>)';
}
$pagingbar .= '</div>';
}
// Okay, let's draw the table of progress info, // Okay, let's draw the table of progress info,
@ -285,16 +261,16 @@ if (!$csv) {
// User heading / sort option // User heading / sort option
print '<th scope="col" class="completion-sortchoice">'; print '<th scope="col" class="completion-sortchoice">';
$sistring = "&amp;silast={$silast}&amp;sifirst={$sifirst}"; $sorturl = fullclone($url);
if ($firstnamesort) { if ($firstnamesort) {
$sorturl->param('sort', 'lastname');
$sortlink = html_writer::link($sorturl, get_string('lastname'));
print print
get_string('firstname')." / <a href=\"./?course={$course->id}{$sistring}\">". get_string('firstname') . " / $sortlink";
get_string('lastname').'</a>';
} else { } else {
print "<a href=\"./?course={$course->id}&amp;sort=firstname{$sistring}\">". $sorturl->param('sort', 'firstname');
get_string('firstname').'</a> / '. $sortlink = html_writer::link($sorturl, get_string('firstname'));
get_string('lastname'); print "$sortlink / " . get_string('lastname');
} }
print '</th>'; print '</th>';
@ -455,10 +431,7 @@ if ($csv) {
print '</tbody></table>'; print '</tbody></table>';
print '</div>'; print '</div>';
print '<ul class="progress-actions"><li><a href="index.php?course='.$course->id. echo $output->render_download_buttons($url);
'&amp;format=csv">'.get_string('csvdownload','completion').'</a></li>
<li><a href="index.php?course='.$course->id.'&amp;format=excelcsv">'.
get_string('excelcsvdownload','completion').'</a></li></ul>';
echo $OUTPUT->footer(); echo $OUTPUT->footer();

View file

@ -24,6 +24,11 @@
*/ */
$string['pluginname'] = 'Activity completion'; $string['pluginname'] = 'Activity completion';
$string['activityorder'] = 'Activity order';
$string['allactivitiesandresources'] = 'All activities and resources';
$string['alphabetical'] = 'Alphabetical ';
$string['include'] = 'Include';
$string['orderincourse'] = 'Order in course';
$string['page-report-progress-x'] = 'Any activity completion report'; $string['page-report-progress-x'] = 'Any activity completion report';
$string['page-report-progress-index'] = 'Activity completion report'; $string['page-report-progress-index'] = 'Activity completion report';
$string['page-report-progress-user'] = 'User activity completion report'; $string['page-report-progress-user'] = 'User activity completion report';

View file

@ -0,0 +1,52 @@
@report @report_progress
Feature: Teacher can view and filter activity completion data by group and activity type.
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion | groupmode |
| Course 1 | C1 | topics | 1 | 1 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | completion | completionview |
| quiz | My quiz B | A3 desc | C1 | quizb | 0 | 2 | 1 |
| quiz | My quiz A | A3 desc | C1 | quiza | 0 | 2 | 1 |
| page | My page | A4 desc | C1 | page1 | 0 | 2 | 1 |
| assign | My assignment | A1 desc | C1 | assign1 | 0 | 2 | 1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | One | teacher1@example.com |
| student1 | Student | One | student1@example.com |
| student2 | Student | Two | student2@example.com |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
| Group 2 | C1 | G2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "group members" exist:
| user | group |
| student1 | G1 |
| student2 | G2 |
| teacher1 | G1 |
| teacher1 | G2 |
@javascript
Scenario: Teacher can view the activity completion report using filtering and sorting options.
When I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Reports > Activity completion" in current page administration
Then "My quiz B" "link" should appear before "My quiz A" "link"
And I should see "My assignment"
And I should see "My page"
And I should see "Student One"
And I should see "Student Two"
And I set the field "Separate groups" to "Group 1"
And I set the field "Include" to "Quizzes"
And I set the field "Activity order" to "Alphabetical"
And "My quiz A" "link" should appear before "My quiz B" "link"
And I should not see "My assignment"
And I should not see "My page"
And I should see "Student One"
And I should not see "Student Two"

View file

@ -0,0 +1,72 @@
<?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/>.
/**
* Tests for the progress report sorting.
*
* @package report_progress
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
defined('MOODLE_INTERNAL') || die();
/**
* Class for testing report progress helper.
*
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
*/
class report_progress_helper_testcase extends advanced_testcase {
/**
* Set up testcase.
*/
public function setUp(): void {
global $CFG;
$CFG->enablecompletion = true;
$this->setAdminUser();
$this->resetAfterTest();
$this->generator = $this->getDataGenerator();
}
/**
* Test process_activities_by_filter_options function.
*/
public function test_sort_activities() {
$expectedactivitytypes = ['all' => 'All activities and resources', 'assign' => 'Assignments', 'quiz' => 'Quizzes'];
// Generate test data.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$quiz2 = $this->generator->create_module('quiz', ['course' => $course->id, 'name' => 'Quiz 2'],
['completion' => 1]);
$quiz1 = $this->generator->create_module('quiz', ['course' => $course->id, 'name' => 'Quiz 1'],
['completion' => 1]);
$assign1 = $this->generator->create_module('assign', ['course' => $course->id, 'name' => 'Assignment'],
['completion' => 1]);
$completion = new completion_info($course);
// Sort the activities by name.
list($activitytypes, $activities) = \report_progress\local\helper::get_activities_to_show($completion, 'quiz',
'alphabetical');
// Check weather the result is filtered and sorted.
$this->assertEquals(2, count($activities));
$this->assertEquals('Quiz 1', $activities[0]->name);
$this->assertEquals('Quiz 2', $activities[1]->name);
$this->assertEquals($activitytypes, $expectedactivitytypes);
}
}