Skip to content

Commit

Permalink
MDL-67686 reportbuilder: new duration filter type.
Browse files Browse the repository at this point in the history
This report filter can be used for filtering reports by given
duration, via selection of time value and unit (seconds, minutes,
hours, etc).
  • Loading branch information
paulholden committed Sep 24, 2021
1 parent 214adb7 commit bd258be
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 0 deletions.
1 change: 1 addition & 0 deletions lang/en/reportbuilder.php
Expand Up @@ -37,6 +37,7 @@
$string['filterdatefrom'] = 'Date from';
$string['filterdateto'] = 'Date to';
$string['filterdoesnotcontain'] = 'Does not contain';
$string['filterdurationunit'] = '{$a} unit';
$string['filterendswith'] = 'Ends with';
$string['filterequalorgreaterthan'] = 'Greater than or equal';
$string['filterequalorlessthan'] = 'Less than or equal';
Expand Down
137 changes: 137 additions & 0 deletions reportbuilder/classes/local/filters/duration.php
@@ -0,0 +1,137 @@
<?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/>.

declare(strict_types=1);

namespace core_reportbuilder\local\filters;

use lang_string;
use MoodleQuickForm;
use core_reportbuilder\local\helpers\database;

/**
* Duration report filter
*
* This filter accepts a number of seconds to perform filtering on
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class duration extends base {

/** @var int Any value */
public const DURATION_ANY = 0;

/** @var int Maximum duration */
public const DURATION_MAXIMUM = 1;

/** @var int Minimum duration */
public const DURATION_MINIMUM = 2;


/**
* Return an array of operators available for this filter
*
* @return lang_string[]
*/
private function get_operators(): array {
$operators = [
self::DURATION_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'),
self::DURATION_MAXIMUM => new lang_string('filterlessthan', 'core_reportbuilder'),
self::DURATION_MINIMUM => new lang_string('filtergreaterthan', 'core_reportbuilder'),
];

return $this->filter->restrict_limited_operators($operators);
}

/**
* Setup form
*
* @param MoodleQuickForm $mform
*/
public function setup_form(MoodleQuickForm $mform): void {
$elements = [];

// Operator.
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());

$elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators());
$mform->setType("{$this->name}_operator", PARAM_INT);
$mform->setDefault("{$this->name}_operator", self::DURATION_ANY);

// Value.
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());

$elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]);
$mform->setType("{$this->name}_value", PARAM_FLOAT);
$mform->setDefault("{$this->name}_value", 0);
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DURATION_ANY);

// Unit.
$unitlabel = get_string('filterdurationunit', 'core_reportbuilder', $this->get_header());
$units = [
1 => get_string('seconds'),
MINSECS => get_string('minutes'),
HOURSECS => get_string('hours'),
DAYSECS => get_string('days'),
];

$elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units);
$mform->setType("{$this->name}_unit", PARAM_INT);
$mform->setDefault("{$this->name}_unit", 1);
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DURATION_ANY);

$mform->addGroup($elements, "{$this->name}_group", '', null, false);
}

/**
* Return filter SQL
*
* @param array $values
* @return array
*/
public function get_sql_filter(array $values): array {
$fieldsql = $this->filter->get_field_sql();
$params = $this->filter->get_field_params();

$durationvalue = unformat_float($values["{$this->name}_value"] ?? 0);
$durationunit = (int) ($values["{$this->name}_unit"] ?? 0);

$operator = $values["{$this->name}_operator"] ?? self::DURATION_ANY;
switch ($operator) {
case self::DURATION_MAXIMUM:
$paramduration = database::generate_param_name();

$sql = "{$fieldsql} <= :{$paramduration}";
$params[$paramduration] = $durationvalue * $durationunit;

break;
case self::DURATION_MINIMUM:
$paramduration = database::generate_param_name();

$sql = "{$fieldsql} >= :{$paramduration}";
$params[$paramduration] = $durationvalue * $durationunit;

break;
default:
// Invalid or inactive filter.
return ['', []];
}

return [$sql, $params];
}
}
128 changes: 128 additions & 0 deletions reportbuilder/tests/local/filters/duration_test.php
@@ -0,0 +1,128 @@
<?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/>.

declare(strict_types=1);

namespace core_reportbuilder\local\filters;

use advanced_testcase;
use lang_string;
use core_reportbuilder\local\report\filter;

/**
* Unit tests for duration report filter
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\filters\base
* @covers \core_reportbuilder\local\filters\duration
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class duration_test extends advanced_testcase {

/**
* Data provider for {@see test_get_sql_filter}
*
* @return array
*/
public function get_sql_filter_provider(): array {
return [
'Any duration' =>
[duration::DURATION_ANY, true],

// Maximum operator.
'Maximum seconds non-match' =>
[duration::DURATION_MAXIMUM, false, HOURSECS, 1],
'Maximum seconds match' =>
[duration::DURATION_MAXIMUM, true, HOURSECS * 3, 1],
'Maximum minutes non-match' =>
[duration::DURATION_MAXIMUM, false, 60, MINSECS],
'Maximum minutes match' =>
[duration::DURATION_MAXIMUM, true, 150, MINSECS],
'Maximum hours non-match (float)' =>
[duration::DURATION_MAXIMUM, false, 0.5, HOURSECS],
'Maximum hours non-match' =>
[duration::DURATION_MAXIMUM, false, 1, HOURSECS],
'Maximum hours match (float)' =>
[duration::DURATION_MAXIMUM, true, 2.5, HOURSECS],
'Maximum hours match' =>
[duration::DURATION_MAXIMUM, true, 3, HOURSECS],

// Minimum operator.
'Minimum seconds match' =>
[duration::DURATION_MINIMUM, true, HOURSECS, 1],
'Minimum seconds non-match' =>
[duration::DURATION_MINIMUM, false, HOURSECS * 3, 1],
'Minimum minutes match' =>
[duration::DURATION_MINIMUM, true, 60, MINSECS],
'Minimum minutes non-match' =>
[duration::DURATION_MINIMUM, false, 150, MINSECS],
'Minimum hours match (float)' =>
[duration::DURATION_MINIMUM, true, 0.5, HOURSECS],
'Minimum hours match' =>
[duration::DURATION_MINIMUM, true, 1, HOURSECS],
'Minimum hours non-match (float)' =>
[duration::DURATION_MINIMUM, false, 2.5, HOURSECS],
'Minimum hours non-match' =>
[duration::DURATION_MINIMUM, false, 3, HOURSECS],
];
}

/**
* Test getting filter SQL
*
* @param int $operator
* @param bool $expectuser
* @param float $value
* @param int $unit
*
* @dataProvider get_sql_filter_provider
*/
public function test_get_sql_filter(int $operator, bool $expectuser, float $value = 0, int $unit = MINSECS): void {
global $DB;

$this->resetAfterTest();

// We are going to enrol our student from now, with a duration of two hours (timeend is two hours later).
$timestart = time();
$timeend = $timestart + (HOURSECS * 2);

$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestart, $timeend);

$filter = new filter(
duration::class,
'test',
new lang_string('yes'),
'testentity',
'timeend - timestart'
);

// Create instance of our filter, passing given values.
[$select, $params] = duration::create($filter)->get_sql_filter([
$filter->get_unique_identifier() . '_operator' => $operator,
$filter->get_unique_identifier() . '_value' => $value,
$filter->get_unique_identifier() . '_unit' => $unit,
]);

$useridfield = $DB->get_field_select('user_enrolments', 'userid', $select, $params);
if ($expectuser) {
$this->assertEquals($useridfield, $user->id);
} else {
$this->assertFalse($useridfield);
}
}
}

0 comments on commit bd258be

Please sign in to comment.