MDL-70795 reportbuilder: editor elements to set column aggregation.

Aggregation of report columns allows the report editor to perform
common types of data aggregation (concatenation, sum, count, etc)
on given columns.
This commit is contained in:
Paul Holden
2021-10-19 13:56:05 +01:00
parent db7102eba5
commit f47e89a9bd
30 changed files with 1812 additions and 21 deletions
+13
View File
@@ -24,7 +24,20 @@
$string['actions'] = 'Actions';
$string['addcolumn'] = 'Add column \'{$a}\'';
$string['aggregatecolumn'] = 'Aggregate column \'{$a}\'';
$string['aggregationavg'] = 'Average';
$string['aggregationcount'] = 'Count';
$string['aggregationcountdistinct'] = 'Count distinct';
$string['aggregationgroupconcat'] = 'Comma separated values';
$string['aggregationgroupconcatdistinct'] = 'Comma separated distinct values';
$string['aggregationmax'] = 'Maximum';
$string['aggregationmin'] = 'Minimum';
$string['aggregationnone'] = 'No aggregation';
$string['aggregationpercent'] = 'Percentage';
$string['aggregationsum'] = 'Sum';
$string['apply'] = 'Apply';
$string['columnadded'] = 'Added column \'{$a}\'';
$string['columnaggregated'] = 'Aggregated column \'{$a}\'';
$string['columnsortdirectionasc'] = 'Sort column \'{$a}\' ascending';
$string['columnsortdirectiondesc'] = 'Sort column \'{$a}\' descending';
$string['columnsortdisable'] = 'Disable sorting for column \'{$a}\'';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@
import $ from 'jquery';
import {dispatchEvent} from 'core/event_dispatcher';
import 'core/inplace_editable';
import {eventTypes as inplaceEditableEvents} from 'core/local/inplace_editable/events';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {publish} from 'core/pubsub';
@@ -142,4 +143,21 @@ export const init = (reportElement, initialized) => {
.catch(Notification.exception);
}
});
// Initialize inplace editable listeners for column aggregation.
reportElement.addEventListener(inplaceEditableEvents.elementUpdated, event => {
const columnAggregation = event.target.closest('[data-itemtype="columnaggregation"]');
if (columnAggregation) {
const columnHeader = columnAggregation.closest(reportSelectors.regions.columnHeader);
getString('columnaggregated', 'core_reportbuilder', columnHeader.dataset.columnName)
.then(addToast)
.then(() => {
dispatchEvent(reportEvents.tableReload, {}, reportElement);
return;
})
.catch(Notification.exception);
}
});
};
@@ -0,0 +1,77 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column average aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class avg extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationavg', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "AVG(1.0 * {$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return sprintf('%.1f', (float) reset($values));
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Base class for column aggregation types
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base {
/**
* Return the class name of the aggregation type
*
* @return string
*/
final public static function get_class_name(): string {
$namespacedclass = explode('\\', get_called_class());
return end($namespacedclass);
}
/**
* Return the display name of the aggregation
*
* @return lang_string
*/
abstract public static function get_name(): lang_string;
/**
* Whether the aggregation is compatible with the given column type
*
* @param int $columntype The type as defined by the {@see column::set_type} method
* @return bool
*/
abstract public static function compatible(int $columntype): bool;
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
abstract public static function get_field_sql(string $field, int $columntype): string;
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
abstract public static function format_value($value, array $values, array $callbacks);
}
@@ -0,0 +1,80 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column count aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class count extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationcount', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all column types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return true;
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$field = $DB->sql_compare_text($field, 255);
}
return "COUNT({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return (int) reset($values);
}
}
@@ -0,0 +1,80 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column count distinct aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class countdistinct extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationcountdistinct', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all column types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return true;
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
if ($columntype === column::TYPE_LONGTEXT && $DB->get_dbfamily() === 'oracle') {
$field = $DB->sql_compare_text($field, 255);
}
return "COUNT(DISTINCT {$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return (int) reset($values);
}
}
@@ -0,0 +1,78 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column group concatenation aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcat extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationgroupconcat', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all non-timestamp columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return !in_array($columntype, [
column::TYPE_TIMESTAMP,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
return $DB->sql_group_concat($field);
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return $value;
}
}
@@ -0,0 +1,90 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column group concatenation distinct aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcatdistinct extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationgroupconcatdistinct', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all non-timestamp columns in MySQL and Postgres only
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
global $DB;
$dbsupportedtype = in_array($DB->get_dbfamily(), [
'mysql',
'postgres',
]);
return $dbsupportedtype && !in_array($columntype, [
column::TYPE_TIMESTAMP,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
global $DB;
// DB limitations mean we only support MySQL and Postgres, and each handle it differently.
if ($DB->get_dbfamily() === 'postgres') {
return "STRING_AGG(DISTINCT CAST({$field} AS VARCHAR), ', ')";
} else {
return $DB->sql_group_concat("DISTINCT {$field}");
}
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return $value;
}
}
@@ -0,0 +1,83 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column max aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class max extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationmax', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric/date/boolean types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_TIMESTAMP,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "MAX({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
foreach ($callbacks as $callback) {
$value = ($callback[0])($value, (object) $values, $callback[1]);
}
return $value;
}
}
@@ -0,0 +1,83 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column min aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class min extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationmin', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric/date/boolean types
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_TIMESTAMP,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "MIN({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
foreach ($callbacks as $callback) {
$value = ($callback[0])($value, (object) $values, $callback[1]);
}
return $value;
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
/**
* Column percent aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class percent extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationpercent', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all boolean columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "AVG(1.0 * {$field}) * 100.0";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return format::percent((float) reset($values));
}
}
@@ -0,0 +1,78 @@
<?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\aggregation;
use lang_string;
use core_reportbuilder\local\report\column;
/**
* Column sum aggregation type
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sum extends base {
/**
* Return aggregation name
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('aggregationsum', 'core_reportbuilder');
}
/**
* This aggregation can be performed on all numeric and boolean columns
*
* @param int $columntype
* @return bool
*/
public static function compatible(int $columntype): bool {
return in_array($columntype, [
column::TYPE_INTEGER,
column::TYPE_FLOAT,
column::TYPE_BOOLEAN,
]);
}
/**
* Return the aggregated field SQL
*
* @param string $field
* @param int $columntype
* @return string
*/
public static function get_field_sql(string $field, int $columntype): string {
return "SUM({$field})";
}
/**
* Return formatted value for column when applying aggregation
*
* @param mixed $value
* @param array $values
* @param array $callbacks
* @return mixed
*/
public static function format_value($value, array $values, array $callbacks) {
return (int) reset($values);
}
}
@@ -0,0 +1,80 @@
<?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\helpers;
use core_collator;
use core_component;
use core_reportbuilder\local\aggregation\base;
/**
* Helper class for column aggregation related methods
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class aggregation {
/**
* Helper method to convert aggregation class name into fully qualified namespaced class
*
* @param string $aggregation
* @return string
*/
public static function get_full_classpath(string $aggregation): string {
return "\\core_reportbuilder\\local\\aggregation\\{$aggregation}";
}
/**
* Validate whether given class is a valid aggregation type
*
* @param string $aggregationclass Fully qualified namespaced class, see {@see get_full_classpath} for converting value
* stored in column persistent to full path
* @return bool
*/
public static function valid(string $aggregationclass): bool {
return class_exists($aggregationclass) && is_subclass_of($aggregationclass, base::class);
}
/**
* Get available aggregation types for given column type
*
* @param int $columntype
* @param array $exclude List of types to exclude, if omitted then include all available types
* @return string[] Aggregation types indexed by [shortname => name]
*/
public static function get_column_aggregations(int $columntype, array $exclude = []): array {
$types = [];
$classes = core_component::get_component_classes_in_namespace('core_reportbuilder', 'local\\aggregation');
foreach ($classes as $class => $path) {
/** @var base $aggregationclass */
$aggregationclass = $class;
if (static::valid($aggregationclass) && $aggregationclass::compatible($columntype) &&
!in_array($aggregationclass::get_class_name(), $exclude)) {
$types[$aggregationclass::get_class_name()] = (string) $aggregationclass::get_name();
}
}
core_collator::asort($types, core_collator::SORT_STRING);
return $types;
}
}
@@ -50,4 +50,14 @@ class format {
public static function boolean_as_text(bool $value): string {
return $value ? get_string('yes') : get_string('no');
}
/**
* Returns float value as a percentage
*
* @param float $value
* @return string
*/
public static function percent(float $value): string {
return sprintf('%.1f', $value) . '%';
}
}
+80 -14
View File
@@ -20,7 +20,9 @@ namespace core_reportbuilder\local\report;
use coding_exception;
use lang_string;
use core_reportbuilder\local\helpers\aggregation;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\aggregation\base;
use core_reportbuilder\local\models\column as column_model;
/**
@@ -63,7 +65,7 @@ final class column {
private $entityname;
/** @var int $type Column data type (one of the TYPE_* class constants) */
private $type = null;
private $type = self::TYPE_TEXT;
/** @var string[] $joins List of SQL joins for this column */
private $joins = [];
@@ -77,6 +79,9 @@ final class column {
/** @var array[] $callbacks Array of [callable, additionalarguments] */
private $callbacks = [];
/** @var base|null $aggregation Aggregation type to apply to column */
private $aggregation = null;
/** @var bool $issortable Used to indicate if a column is sortable */
private $issortable = false;
@@ -175,7 +180,7 @@ final class column {
* Set the column index within the current report
*
* @param int $index
* @return column
* @return self
*/
public function set_index(int $index): self {
$this->index = $index;
@@ -183,10 +188,13 @@ final class column {
}
/**
* Set the column type
* Set the column type, if not called then the type will be assumed to be {@see TYPE_TEXT}
*
* The type of a column is used to cast the first column field passed to any callbacks {@see add_callback} as well as the
* aggregation options available for the column
*
* @param int $type
* @return column
* @return self
* @throws coding_exception
*/
public function set_type(int $type): self {
@@ -207,11 +215,11 @@ final class column {
}
/**
* Return column type
* Return column type, that being one of the TYPE_* class constants
*
* @return int|null
* @return int
*/
public function get_type(): ?int {
public function get_type(): int {
return $this->type;
}
@@ -359,13 +367,38 @@ final class column {
* @return array
*/
public function get_fields(): array {
$fields = array_map(static function(array $field): string {
return "{$field['sql']} AS {$field['alias']}";
}, $this->get_fields_sql_alias());
$fieldsalias = $this->get_fields_sql_alias();
// We aggregate the first field only.
if (!empty($this->aggregation)) {
$field = reset($fieldsalias);
$fields = [$this->aggregation::get_field_sql($field['sql'], $this->get_type()) . " AS {$field['alias']}"];
} else {
$fields = array_map(static function(array $field): string {
return "{$field['sql']} AS {$field['alias']}";
}, $fieldsalias);
}
return array_values($fields);
}
/**
* Return suitable SQL fragment for grouping by the column fields (during aggregation)
*
* @return array
*/
public function get_groupby_sql(): array {
global $DB;
$fieldsalias = $this->get_fields_sql_alias();
// Note that we can reference field aliases in GROUP BY only in MySQL/Postgres.
$usealias = in_array($DB->get_dbfamily(), ['mysql', 'postgres']);
$columnname = $usealias ? 'alias' : 'sql';
return array_column($fieldsalias, $columnname);
}
/**
* Return column parameters, prefixed by the current index to allow the column to be added multiple times to a report
*
@@ -402,6 +435,8 @@ final class column {
* The callback should implement the following signature (where $value is the first column field, $row is all column
* fields, and $additionalarguments are those passed on from this method):
*
* The type of the $value parameter passed to the callback is determined by calling {@see set_type}
*
* function($value, stdClass $row[, $additionalarguments]): string
*
* @param callable $callable function that takes arguments ($value, \stdClass $row, $additionalarguments)
@@ -425,6 +460,34 @@ final class column {
return $this->add_callback($callable, $additionalarguments);
}
/**
* Set column aggregation type
*
* @param string|null $aggregation Type of aggregation, e.g. 'sum', 'count', etc
* @return self
* @throws coding_exception For invalid aggregation type, or one that is incompatible with column type
*/
public function set_aggregation(?string $aggregation): self {
if (!empty($aggregation)) {
$aggregation = aggregation::get_full_classpath($aggregation);
if (!aggregation::valid($aggregation) || !$aggregation::compatible($this->get_type())) {
throw new coding_exception('Invalid column aggregation', $aggregation);
}
}
$this->aggregation = $aggregation;
return $this;
}
/**
* Get column aggregation type
*
* @return base|null
*/
public function get_aggregation(): ?string {
return $this->aggregation;
}
/**
* Sets the column as sortable
*
@@ -497,9 +560,13 @@ final class column {
$values = $this->get_values($row);
$value = $this->get_default_value($values);
// Loop through, and apply any defined callbacks.
foreach ($this->callbacks as $callback) {
$value = ($callback[0])($value, (object) $values, $callback[1]);
// If column is being aggregated then defer formatting to them, otherwise loop through all column callbacks.
if (!empty($this->aggregation)) {
$value = $this->aggregation::format_value($value, $values, $this->callbacks);
} else {
foreach ($this->callbacks as $callback) {
$value = ($callback[0])($value, (object) $values, $callback[1]);
}
}
return $value;
@@ -546,7 +613,6 @@ final class column {
return $this;
}
/**
* Set column persistent
*
@@ -0,0 +1,85 @@
<?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\output;
use core_external;
use core\output\inplace_editable;
use core_reportbuilder\manager;
use core_reportbuilder\permission;
use core_reportbuilder\local\helpers\aggregation;
use core_reportbuilder\local\models\column;
/**
* Column aggregation editable component
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_aggregation_editable extends inplace_editable {
/**
* Class constructor
*
* @param int $columnid
* @param column|null $column
*/
public function __construct(int $columnid, ?column $column = null) {
if ($column === null) {
$column = new column($columnid);
}
$report = $column->get_report();
$editable = permission::can_edit_report($report);
$columninstance = manager::get_report_from_persistent($report)
->get_column($column->get('uniqueidentifier'));
$currentvalue = (string) $column->get('aggregation');
parent::__construct('core_reportbuilder', 'columnaggregation', $column->get('id'), $editable, null, $currentvalue,
get_string('aggregatecolumn', 'core_reportbuilder', $columninstance->get_title()));
$options = aggregation::get_column_aggregations($columninstance->get_type());
$this->set_type_select(['' => get_string('aggregationnone', 'core_reportbuilder')] + $options);
}
/**
* Update column persistent and return self, called from inplace_editable callback
*
* @param int $columnid
* @param string $value
* @return self
*/
public static function update(int $columnid, string $value): self {
$column = new column($columnid);
$report = $column->get_report();
core_external::validate_context($report->get_context());
permission::require_can_edit_report($report);
$value = clean_param($value, PARAM_TEXT);
$column
->set('aggregation', $value)
->update();
return new self(0, $column);
}
}
@@ -81,10 +81,21 @@ class custom_report_table extends base_report_table {
return;
}
// If we are aggregating any columns, we should group by the remaining ones.
$aggregatedcolumns = array_filter($columns, static function(column $column): bool {
return !empty($column->get_aggregation());
});
$hasaggregatedcolumns = !empty($aggregatedcolumns);
$columnheaders = [];
foreach ($columns as $column) {
$columnheaders[$column->get_column_alias()] = $column->get_persistent()->get('heading') ?: $column->get_title();
$columnaggregation = $column->get_aggregation();
if ($hasaggregatedcolumns && empty($columnaggregation)) {
$groupby = array_merge($groupby, $column->get_groupby_sql());
}
// Add each columns fields, joins and params to our report.
$fields = array_merge($fields, $column->get_fields());
$joins = array_merge($joins, $column->get_joins());
@@ -189,13 +200,17 @@ class custom_report_table extends base_report_table {
$column->set_persistent($instance);
// We should clone the report column to ensure if it's added twice to a report, each operates independently.
$columns[$instance->get('id')] = clone $column
->set_index($index);
->set_index($index)
->set_aggregation($instance->get('aggregation'));
}
}
return $columns;
}
/**
* Override parent method for printing headers so we can render our custom controls in each cell
*/
public function print_headers() {
global $OUTPUT, $PAGE;
@@ -214,13 +229,14 @@ class custom_report_table extends base_report_table {
$column = $columns[$index];
$headingeditable = new column_heading_editable(0, $column->get_persistent());
$aggregationeditable = new column_aggregation_editable(0, $column->get_persistent());
// Render table header cell, with all editing controls.
$headercell = $OUTPUT->render_from_template('core_reportbuilder/table_header_cell', [
'entityname' => $this->report->get_entity_title($column->get_entity_name()),
'name' => $column->get_title(),
'headingeditable' => $headingeditable->render($renderer),
'aggregationeditable' => 'Aggregation',
'aggregationeditable' => $aggregationeditable->render($renderer),
'movetitle' => get_string('movecolumn', 'core_reportbuilder', $column->get_title()),
]);
+3
View File
@@ -61,6 +61,9 @@ function core_reportbuilder_inplace_editable($itemtype, $itemid, $newvalue) {
case 'columnheading':
return \core_reportbuilder\output\column_heading_editable::update($itemid, $newvalue);
case 'columnaggregation':
return \core_reportbuilder\output\column_aggregation_editable::update($itemid, $newvalue);
case 'filterheading':
return \core_reportbuilder\output\filter_heading_editable::update($itemid, $newvalue);
+54
View File
@@ -0,0 +1,54 @@
<?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);
use core_reportbuilder\table\custom_report_table_view;
/**
* Helper base class for reportbuilder unit tests
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class core_reportbuilder_testcase extends advanced_testcase {
/**
* Retrieve content for given report as array of report data
*
* @param int $reportid
* @param int $pagesize
* @return array[]
*/
protected function get_custom_report_content(int $reportid, int $pagesize = 30): array {
$records = [];
// Create table instance.
$table = custom_report_table_view::create($reportid);
$table->setup();
$table->query_db($pagesize, false);
// Extract raw data.
foreach ($table->rawdata as $record) {
$records[] = $table->format_row($record);
}
$table->close_recordset();
return $records;
}
}
@@ -0,0 +1,78 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for count aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\count
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class count_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname'])
->set('aggregation', count::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_lastname' => 1,
],
[
'c0_firstname' => 'Bob',
'c1_lastname' => 3,
],
], $content);
}
}
@@ -0,0 +1,78 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for count distinct aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\countdistinct
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class countdistinct_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname'])
->set('aggregation', countdistinct::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_lastname' => 1,
],
[
'c0_firstname' => 'Bob',
'c1_lastname' => 2,
],
], $content);
}
}
@@ -0,0 +1,80 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for group concatenation aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\groupconcat
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcat_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname'])
->set('aggregation', groupconcat::get_class_name())
->update();
[$firstrow, $secondrow] = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
'c0_firstname' => 'Admin',
'c1_lastname' => 'User',
], $firstrow);
// Currently the aggregated field sort order is undefined.
$this->assertEquals('Bob', $secondrow['c0_firstname']);
$this->assertEqualsCanonicalizing([
'Apple',
'Banana',
'Banana',
], explode(', ', $secondrow['c1_lastname']));
}
}
@@ -0,0 +1,91 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for group concatenation distinct aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\groupconcatdistinct
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupconcatdistinct_test extends core_reportbuilder_testcase {
/**
* Test setup, we need to skip these tests on non-supported databases
*/
public function setUp(): void {
global $DB;
$dbfamily = $DB->get_dbfamily();
if (!in_array($dbfamily, ['mysql', 'postgres'])) {
$this->markTestSkipped("Distinct group concatenation not supported in {$dbfamily}");
}
}
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Apple']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'lastname' => 'Banana']);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastname'])
->set('aggregation', groupconcatdistinct::get_class_name())
->update();
[$firstrow, $secondrow] = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
'c0_firstname' => 'Admin',
'c1_lastname' => 'User',
], $firstrow);
// Currently the aggregated field sort order is undefined.
$this->assertEquals('Bob', $secondrow['c0_firstname']);
$this->assertEqualsCanonicalizing([
'Apple',
'Banana',
], explode(', ', $secondrow['c1_lastname']));
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for max aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\max
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class max_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended'])
->set('aggregation', max::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => 'No',
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => 'Yes',
],
], $content);
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for min aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\min
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class min_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended'])
->set('aggregation', min::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => 'No',
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => 'No',
],
], $content);
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for sum aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\percent
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class percent_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 0]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended'])
->set('aggregation', percent::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => '0.0%',
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => '50.0%',
],
], $content);
}
}
@@ -0,0 +1,77 @@
<?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\aggregation;
use core_reportbuilder_testcase;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for percent aggregation
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\aggregation\base
* @covers \core_reportbuilder\local\aggregation\sum
* @copyright 2021 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sum_test extends core_reportbuilder_testcase {
/**
* Test aggregation when applied to column
*/
public function test_column_aggregation(): void {
$this->resetAfterTest();
// Test subjects.
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
$this->getDataGenerator()->create_user(['firstname' => 'Bob', 'suspended' => 1]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Users', 'source' => users::class, 'default' => 0]);
// First column, sorted.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:firstname'])
->set('sortenabled', true)
->update();
// This is the column we'll aggregate.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:suspended'])
->set('aggregation', sum::get_class_name())
->update();
$content = $this->get_custom_report_content($report->get('id'));
$this->assertEquals([
[
'c0_firstname' => 'Admin',
'c1_suspended' => 0,
],
[
'c0_firstname' => 'Bob',
'c1_suspended' => 2,
],
], $content);
}
}
@@ -32,7 +32,7 @@ use core_reportbuilder\local\helpers\database;
* @copyright 2020 Paul Holden <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_testcase extends advanced_testcase {
class column_test extends advanced_testcase {
/**
* Test column name getter/setter
@@ -86,11 +86,19 @@ class column_testcase extends advanced_testcase {
*/
public function test_type(): void {
$column = $this->create_column('test');
$this->assertEquals(column::TYPE_TEXT, $column
->set_type(column::TYPE_TEXT)
$this->assertEquals(column::TYPE_INTEGER, $column
->set_type(column::TYPE_INTEGER)
->get_type());
}
/**
* Test column default type
*/
public function test_type_default(): void {
$column = $this->create_column('test');
$this->assertEquals(column::TYPE_TEXT, $column->get_type());
}
/**
* Test column type with invalid value
*/