mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
Merge branch 'MDL-75191' of https://github.com/paulholden/moodle
This commit is contained in:
commit
87db68a0ce
9 changed files with 621 additions and 43 deletions
|
@ -18,6 +18,7 @@ namespace core_admin\local\entities;
|
|||
|
||||
use core_reportbuilder\local\filters\date;
|
||||
use core_reportbuilder\local\filters\duration;
|
||||
use core_reportbuilder\local\filters\number;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_reportbuilder\local\filters\text;
|
||||
use core_reportbuilder\local\filters\autocomplete;
|
||||
|
@ -62,15 +63,6 @@ class task_log extends base {
|
|||
return new lang_string('entitytasklog', 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* The default machine-readable name for this entity that will be used in the internal names of the columns/filters
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_default_entity_name(): string {
|
||||
return 'task_log';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the entity
|
||||
*
|
||||
|
@ -82,9 +74,12 @@ class task_log extends base {
|
|||
$this->add_column($column);
|
||||
}
|
||||
|
||||
// All the filters defined by the entity can also be used as conditions.
|
||||
$filters = $this->get_all_filters();
|
||||
foreach ($filters as $filter) {
|
||||
$this->add_filter($filter);
|
||||
$this
|
||||
->add_filter($filter)
|
||||
->add_condition($filter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -96,6 +91,7 @@ class task_log extends base {
|
|||
* @return column[]
|
||||
*/
|
||||
protected function get_all_columns(): array {
|
||||
global $DB;
|
||||
|
||||
$tablealias = $this->get_table_alias('task_log');
|
||||
|
||||
|
@ -118,11 +114,22 @@ class task_log extends base {
|
|||
}
|
||||
}
|
||||
$output .= \html_writer::tag('div', "\\{$classname}", [
|
||||
'class' => 'task-class',
|
||||
'class' => 'small text-muted',
|
||||
]);
|
||||
return $output;
|
||||
});
|
||||
|
||||
// Component column.
|
||||
$columns[] = (new column(
|
||||
'component',
|
||||
new lang_string('plugin'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_field("{$tablealias}.component")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Type column.
|
||||
$columns[] = (new column(
|
||||
'type',
|
||||
|
@ -130,11 +137,11 @@ class task_log extends base {
|
|||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->set_type(column::TYPE_TEXT)
|
||||
->add_field("{$tablealias}.type")
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function(int $value): string {
|
||||
if (\core\task\database_logger::TYPE_SCHEDULED === $value) {
|
||||
->add_callback(static function($value): string {
|
||||
if (\core\task\database_logger::TYPE_SCHEDULED === (int) $value) {
|
||||
return get_string('task_type:scheduled', 'admin');
|
||||
}
|
||||
return get_string('task_type:adhoc', 'admin');
|
||||
|
@ -152,6 +159,18 @@ class task_log extends base {
|
|||
->set_is_sortable(true)
|
||||
->add_callback([format::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
|
||||
|
||||
// End time column.
|
||||
$columns[] = (new column(
|
||||
'endtime',
|
||||
new lang_string('task_endtime', 'admin'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_TIMESTAMP)
|
||||
->add_field("{$tablealias}.timeend")
|
||||
->set_is_sortable(true)
|
||||
->add_callback([format::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
|
||||
|
||||
// Duration column.
|
||||
$columns[] = (new column(
|
||||
'duration',
|
||||
|
@ -192,7 +211,9 @@ class task_log extends base {
|
|||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_field("{$tablealias}.pid")
|
||||
->set_is_sortable(true);
|
||||
->set_is_sortable(true)
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database column.
|
||||
$columns[] = (new column(
|
||||
|
@ -203,13 +224,37 @@ class task_log extends base {
|
|||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
|
||||
->set_is_sortable(true)
|
||||
->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
|
||||
->add_callback(static function(int $value, stdClass $row): string {
|
||||
$output = '';
|
||||
$output .= \html_writer::div(get_string('task_stats:dbreads', 'admin', $row->dbreads));
|
||||
$output .= \html_writer::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites));
|
||||
return $output;
|
||||
});
|
||||
})
|
||||
// Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
|
||||
->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
|
||||
|
||||
// Database reads column.
|
||||
$columns[] = (new column(
|
||||
'dbreads',
|
||||
new lang_string('task_dbreads', 'admin'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_fields("{$tablealias}.dbreads")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Database writes column.
|
||||
$columns[] = (new column(
|
||||
'dbwrites',
|
||||
new lang_string('task_dbwrites', 'admin'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_fields("{$tablealias}.dbwrites")
|
||||
->set_is_sortable(true);
|
||||
|
||||
// Result column.
|
||||
$columns[] = (new column(
|
||||
|
@ -218,11 +263,12 @@ class task_log extends base {
|
|||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->add_field("$tablealias.result")
|
||||
->set_type(column::TYPE_BOOLEAN)
|
||||
// For accurate aggregation, we need to return boolean success = true by xor'ing the field value.
|
||||
->add_field($DB->sql_bitxor("{$tablealias}.result", 1), 'success')
|
||||
->set_is_sortable(true)
|
||||
->add_callback(static function(int $value): string {
|
||||
if ($value) {
|
||||
->add_callback(static function(bool $success): string {
|
||||
if (!$success) {
|
||||
return get_string('task_result:failed', 'admin');
|
||||
}
|
||||
return get_string('success');
|
||||
|
@ -266,6 +312,16 @@ class task_log extends base {
|
|||
return $options;
|
||||
});
|
||||
|
||||
// Component filter.
|
||||
$filters[] = (new filter(
|
||||
text::class,
|
||||
'component',
|
||||
new lang_string('plugin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.component"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Type filter.
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
|
@ -290,20 +346,6 @@ class task_log extends base {
|
|||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Result filter.
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
'result',
|
||||
new lang_string('task_result', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.result"
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_options([
|
||||
self::SUCCESS => get_string('success'),
|
||||
self::FAILED => get_string('task_result:failed', 'admin'),
|
||||
]);
|
||||
|
||||
// Start time filter.
|
||||
$filters[] = (new filter(
|
||||
date::class,
|
||||
|
@ -320,16 +362,66 @@ class task_log extends base {
|
|||
date::DATE_CURRENT,
|
||||
]);
|
||||
|
||||
// End time.
|
||||
$filters[] = (new filter(
|
||||
date::class,
|
||||
'timeend',
|
||||
new lang_string('task_endtime', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.timeend"
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_limited_operators([
|
||||
date::DATE_ANY,
|
||||
date::DATE_RANGE,
|
||||
date::DATE_PREVIOUS,
|
||||
date::DATE_CURRENT,
|
||||
]);
|
||||
|
||||
// Duration filter.
|
||||
$filters[] = (new filter(
|
||||
duration::class,
|
||||
'duration',
|
||||
new lang_string('task_duration', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"${tablealias}.timeend - {$tablealias}.timestart"
|
||||
"{$tablealias}.timeend - {$tablealias}.timestart"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Database reads.
|
||||
$filters[] = (new filter(
|
||||
number::class,
|
||||
'dbreads',
|
||||
new lang_string('task_dbreads', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.dbreads"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Database writes.
|
||||
$filters[] = (new filter(
|
||||
number::class,
|
||||
'dbwrites',
|
||||
new lang_string('task_dbwrites', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.dbwrites"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
|
||||
// Result filter.
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
'result',
|
||||
new lang_string('task_result', 'admin'),
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.result"
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
->set_options([
|
||||
self::SUCCESS => get_string('success'),
|
||||
self::FAILED => get_string('task_result:failed', 'admin'),
|
||||
]);
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
|
|
117
admin/classes/reportbuilder/datasource/task_logs.php
Normal file
117
admin/classes/reportbuilder/datasource/task_logs.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?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_admin\reportbuilder\datasource;
|
||||
|
||||
use core_reportbuilder\datasource;
|
||||
use core_reportbuilder\local\entities\user;
|
||||
use core_reportbuilder\local\filters\select;
|
||||
use core_admin\local\entities\task_log;
|
||||
|
||||
/**
|
||||
* Task logs datasource
|
||||
*
|
||||
* @package core_admin
|
||||
* @copyright 2022 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class task_logs extends datasource {
|
||||
|
||||
/**
|
||||
* Return user friendly name of the report source
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('tasklogs', 'core_admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise report
|
||||
*/
|
||||
protected function initialise(): void {
|
||||
$tasklogentity = new task_log();
|
||||
|
||||
$tasklogalias = $tasklogentity->get_table_alias('task_log');
|
||||
$this->set_main_table('task_log', $tasklogalias);
|
||||
|
||||
$this->add_entity($tasklogentity);
|
||||
|
||||
// Join the user entity to represent the associated user.
|
||||
$userentity = new user();
|
||||
$useralias = $userentity->get_table_alias('user');
|
||||
$this->add_entity($userentity->add_join("
|
||||
LEFT JOIN {user} {$useralias}
|
||||
ON {$useralias}.id = {$tasklogalias}.userid")
|
||||
);
|
||||
|
||||
// Add report elements from each of the entities we added to the report.
|
||||
$this->add_all_from_entities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_columns(): array {
|
||||
return [
|
||||
'task_log:name',
|
||||
'task_log:starttime',
|
||||
'task_log:duration',
|
||||
'task_log:result',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filters that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_filters(): array {
|
||||
return [
|
||||
'task_log:timestart',
|
||||
'task_log:result',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the conditions that will be added to the report upon creation
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_default_conditions(): array {
|
||||
return [
|
||||
'task_log:type',
|
||||
'task_log:timestart',
|
||||
'task_log:result',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the condition values that will be set for the report upon creation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_default_condition_values(): array {
|
||||
return [
|
||||
'task_log:type_operator' => select::EQUAL_TO,
|
||||
'task_log:type_value' => \core\task\database_logger::TYPE_SCHEDULED,
|
||||
];
|
||||
}
|
||||
}
|
259
admin/tests/reportbuilder/datasource/task_logs_test.php
Normal file
259
admin/tests/reportbuilder/datasource/task_logs_test.php
Normal file
|
@ -0,0 +1,259 @@
|
|||
<?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_admin\reportbuilder\datasource;
|
||||
|
||||
use core\task\database_logger;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder_testcase;
|
||||
use core_reportbuilder\local\filters\{boolean_select, date, duration, number, select, text};
|
||||
use core_reportbuilder\task\send_schedules;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
|
||||
|
||||
/**
|
||||
* Unit tests for task logs datasource
|
||||
*
|
||||
* @package core_admin
|
||||
* @covers \core_admin\reportbuilder\datasource\task_logs
|
||||
* @copyright 2022 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class task_logs_test extends core_reportbuilder_testcase {
|
||||
|
||||
/**
|
||||
* Test default datasource
|
||||
*/
|
||||
public function test_datasource_default(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->generate_task_log_data(true, 3, 2, 1654038000, 1654038060);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
$report = $generator->create_report(['name' => 'Tasks', 'source' => task_logs::class, 'default' => 1]);
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertCount(1, $content);
|
||||
|
||||
// Default columns are name, starttime, duration, result.
|
||||
[$name, $timestart, $duration, $result] = array_values($content[0]);
|
||||
$this->assertStringContainsString(send_schedules::class, $name);
|
||||
$this->assertEquals('1/06/22, 07:00:00', $timestart);
|
||||
$this->assertEquals('1 min', $duration);
|
||||
$this->assertEquals('Success', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test datasource columns that aren't added by default
|
||||
*/
|
||||
public function test_datasource_non_default_columns(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->generate_task_log_data(true, 3, 2, 1654038000, 1654038060, 'hi', 'core_reportbuilder', 'test', 43);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
$report = $generator->create_report(['name' => 'Tasks', 'source' => task_logs::class, 'default' => 0]);
|
||||
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:component']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:type']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:endtime']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:hostname']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:pid']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:database']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:dbreads']);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:dbwrites']);
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertCount(1, $content);
|
||||
|
||||
$this->assertEquals([
|
||||
'core_reportbuilder',
|
||||
'Scheduled',
|
||||
'1/06/22, 07:01:00',
|
||||
'test',
|
||||
'43',
|
||||
'<div>3 reads</div><div>2 writes</div>',
|
||||
'3',
|
||||
'2',
|
||||
], array_values($content[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_datasource_filters}
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function datasource_filters_provider(): array {
|
||||
return [
|
||||
'Filter name' => ['task_log:name', [
|
||||
'task_log:name_values' => [send_schedules::class],
|
||||
], true],
|
||||
'Filter name (no match)' => ['task_log:name', [
|
||||
'task_log:name_values' => ['invalid'],
|
||||
], false],
|
||||
'Filter component' => ['task_log:component', [
|
||||
'task_log:component_operator' => select::EQUAL_TO,
|
||||
'task_log:component_value' => 'core_reportbuilder',
|
||||
], true],
|
||||
'Filter component (no match)' => ['task_log:component', [
|
||||
'task_log:component_operator' => select::NOT_EQUAL_TO,
|
||||
'task_log:component_value' => 'core_reportbuilder',
|
||||
], false],
|
||||
'Filter type' => ['task_log:type', [
|
||||
'task_log:type_operator' => select::EQUAL_TO,
|
||||
'task_log:type_value' => database_logger::TYPE_SCHEDULED,
|
||||
], true],
|
||||
'Filter type (no match)' => ['task_log:type', [
|
||||
'task_log:type_operator' => select::EQUAL_TO,
|
||||
'task_log:type_value' => database_logger::TYPE_ADHOC,
|
||||
], false],
|
||||
'Filter output' => ['task_log:output', [
|
||||
'task_log:output_operator' => text::IS_NOT_EMPTY,
|
||||
], true],
|
||||
'Filter output (no match)' => ['task_log:output', [
|
||||
'task_log:output_operator' => text::IS_EMPTY,
|
||||
], false],
|
||||
'Filter result' => ['task_log:result', [
|
||||
'task_log:result_operator' => boolean_select::CHECKED,
|
||||
], true],
|
||||
'Filter result (no match)' => ['task_log:result', [
|
||||
'task_log:result_operator' => boolean_select::NOT_CHECKED,
|
||||
], false],
|
||||
'Filter time start' => ['task_log:timestart', [
|
||||
'task_log:timestart_operator' => date::DATE_RANGE,
|
||||
'task_log:timestart_from' => 1622502000,
|
||||
], true],
|
||||
'Filter time start (no match)' => ['task_log:timestart', [
|
||||
'task_log:timestart_operator' => date::DATE_RANGE,
|
||||
'task_log:timestart_to' => 1622502000,
|
||||
], false],
|
||||
'Filter time end' => ['task_log:timeend', [
|
||||
'task_log:timeend_operator' => date::DATE_RANGE,
|
||||
'task_log:timeend_from' => 1622502000,
|
||||
], true],
|
||||
'Filter time end (no match)' => ['task_log:timeend', [
|
||||
'task_log:timeend_operator' => date::DATE_RANGE,
|
||||
'task_log:timeend_to' => 1622502000,
|
||||
], false],
|
||||
'Filter duration' => ['task_log:duration', [
|
||||
'task_log:duration_operator' => duration::DURATION_MAXIMUM,
|
||||
'task_log:duration_unit' => MINSECS,
|
||||
'task_log:duration_value' => 2,
|
||||
], true],
|
||||
'Filter duration (no match)' => ['task_log:duration', [
|
||||
'task_log:duration_operator' => duration::DURATION_MINIMUM,
|
||||
'task_log:duration_unit' => MINSECS,
|
||||
'task_log:duration_value' => 2,
|
||||
], false],
|
||||
'Filter database reads' => ['task_log:dbreads', [
|
||||
'task_log:dbreads_operator' => number::LESS_THAN,
|
||||
'task_log:dbreads_value1' => 4,
|
||||
], true],
|
||||
'Filter database reads (no match)' => ['task_log:dbreads', [
|
||||
'task_log:dbreads_operator' => number::GREATER_THAN,
|
||||
'task_log:dbreads_value1' => 4,
|
||||
], false],
|
||||
'Filter database writes' => ['task_log:dbwrites', [
|
||||
'task_log:dbwrites_operator' => number::LESS_THAN,
|
||||
'task_log:dbwrites_value1' => 4,
|
||||
], true],
|
||||
'Filter database writes (no match)' => ['task_log:dbwrites', [
|
||||
'task_log:dbwrites_operator' => number::GREATER_THAN,
|
||||
'task_log:dbwrites_value1' => 4,
|
||||
], false],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test datasource filters
|
||||
*
|
||||
* @param string $filtername
|
||||
* @param array $filtervalues
|
||||
* @param bool $expectmatch
|
||||
*
|
||||
* @dataProvider datasource_filters_provider
|
||||
*/
|
||||
public function test_datasource_filters(
|
||||
string $filtername,
|
||||
array $filtervalues,
|
||||
bool $expectmatch
|
||||
): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->generate_task_log_data(true, 3, 2, 1654038000, 1654038060, 'hi', 'core_reportbuilder', 'test', 43);
|
||||
|
||||
/** @var core_reportbuilder_generator $generator */
|
||||
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
|
||||
|
||||
// Create report containing single component column, and given filter.
|
||||
$report = $generator->create_report(['name' => 'Tasks', 'source' => task_logs::class, 'default' => 0]);
|
||||
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'task_log:component']);
|
||||
|
||||
// Add filter, set it's values.
|
||||
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
|
||||
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
|
||||
|
||||
if ($expectmatch) {
|
||||
$this->assertCount(1, $content);
|
||||
$this->assertEquals('core_reportbuilder', reset($content[0]));
|
||||
} else {
|
||||
$this->assertEmpty($content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to generate some task logs data
|
||||
*
|
||||
* @param bool $success
|
||||
* @param int $dbreads
|
||||
* @param int $dbwrites
|
||||
* @param float $timestart
|
||||
* @param float $timeend
|
||||
* @param string $logoutput
|
||||
* @param string $component
|
||||
* @param string $hostname
|
||||
* @param int $pid
|
||||
*/
|
||||
private function generate_task_log_data(
|
||||
bool $success,
|
||||
int $dbreads,
|
||||
int $dbwrites,
|
||||
float $timestart,
|
||||
float $timeend,
|
||||
string $logoutput = 'hello',
|
||||
string $component = 'moodle',
|
||||
string $hostname = 'phpunit',
|
||||
int $pid = 42
|
||||
): void {
|
||||
|
||||
$logpath = make_request_directory() . '/log.txt';
|
||||
file_put_contents($logpath, $logoutput);
|
||||
|
||||
$task = new send_schedules();
|
||||
$task->set_component($component);
|
||||
$task->set_hostname($hostname);
|
||||
$task->set_pid($pid);
|
||||
|
||||
database_logger::store_log_for_task($task, $logpath, !$success, $dbreads, $dbwrites, $timestart, $timeend);
|
||||
}
|
||||
}
|
|
@ -1306,6 +1306,7 @@ $string['task_adhoc_concurrency_limit'] = 'Ad hoc task concurrency limit';
|
|||
$string['task_adhoc_concurrency_limit_desc'] = 'The number of ad hoc task runners allowed to run concurrently. If the limit is high then scheduled tasks may not run regularly when there are lots of ad hoc tasks. A setting of 0 will disable processing of ad hoc tasks completely.';
|
||||
$string['task_adhoc_max_runtime'] = 'Ad hoc task runner lifetime';
|
||||
$string['task_adhoc_max_runtime_desc'] = 'The age of an ad hoc task runner before it is freed. A low duration is recommended as there is no limit to the number of ad hoc tasks queued. If this number is too high and you have a large ad hoc task queue then scheduled tasks may not be run regularly.';
|
||||
$string['task_endtime'] = 'End time';
|
||||
$string['task_logmode'] = 'When to log';
|
||||
$string['task_logmode_desc'] = 'You can choose when you wish task logging to take place. By default logs are always captured. You can disable logging entirely, or change to only log tasks which fail.';
|
||||
$string['task_logmode_none'] = 'Do not log anything';
|
||||
|
@ -1327,7 +1328,9 @@ $string['task_status'] = 'Task status';
|
|||
$string['task_status_desc'] = 'The task \'{$a->name}\' is {$a->status}. For details, see {$a->class}{$a->extradescription} in <a href="{$a->gotourl}">Scheduled tasks</a>.';
|
||||
$string['task_starttime'] = 'Start time';
|
||||
$string['task_duration'] = 'Duration';
|
||||
$string['task_dbreads'] = 'Database reads';
|
||||
$string['task_dbstats'] = 'Database';
|
||||
$string['task_dbwrites'] = 'Database writes';
|
||||
$string['task_result'] = 'Result';
|
||||
$string['tasktype'] = 'Type';
|
||||
$string['tasklockcleanuptask'] = 'Clean up ad hoc task metadata';
|
||||
|
|
|
@ -67,13 +67,19 @@ class avg extends base {
|
|||
/**
|
||||
* Return formatted value for column when applying aggregation
|
||||
*
|
||||
* For boolean columns we return the average of the values (0..1), numeric columns execute original callbacks if present
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array $values
|
||||
* @param array $callbacks
|
||||
* @param int $columntype
|
||||
* @return string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function format_value($value, array $values, array $callbacks, int $columntype): string {
|
||||
return format_float((float) reset($values), 1);
|
||||
public static function format_value($value, array $values, array $callbacks, int $columntype) {
|
||||
if ($columntype === column::TYPE_BOOLEAN || empty($callbacks)) {
|
||||
return format_float((float) reset($values), 1);
|
||||
}
|
||||
|
||||
return parent::format_value($value, $values, $callbacks, $columntype);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,13 +67,20 @@ class sum extends base {
|
|||
/**
|
||||
* Return formatted value for column when applying aggregation
|
||||
*
|
||||
* For boolean columns we return the sum of the true values, numeric columns execute original callbacks if present
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array $values
|
||||
* @param array $callbacks
|
||||
* @param int $columntype
|
||||
* @return int
|
||||
* @return mixed
|
||||
*/
|
||||
public static function format_value($value, array $values, array $callbacks, int $columntype): int {
|
||||
return (int) reset($values);
|
||||
public static function format_value($value, array $values, array $callbacks, int $columntype) {
|
||||
if ($columntype === column::TYPE_BOOLEAN || empty($callbacks)) {
|
||||
$decimalpoints = (int) ($columntype === column::TYPE_FLOAT);
|
||||
return format_float((float) reset($values), $decimalpoints);
|
||||
}
|
||||
|
||||
return parent::format_value($value, $values, $callbacks, $columntype);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -479,7 +479,7 @@ final class 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}, this type is preserved
|
||||
* if the column is part of a report source and is aggregated using one of the "Group concatenation" methods
|
||||
* if the column is part of a report source and is being aggregated
|
||||
*
|
||||
* function($value, stdClass $row[, $additionalarguments]): string
|
||||
*
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace core_reportbuilder\local\aggregation;
|
|||
|
||||
use core_reportbuilder_testcase;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_user\reportbuilder\datasource\users;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
@ -73,4 +75,49 @@ class avg_test extends core_reportbuilder_testcase {
|
|||
],
|
||||
], $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test aggregation when applied to column with callback
|
||||
*/
|
||||
public function test_column_aggregation_with_callback(): 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', avg::get_class_name())
|
||||
->update();
|
||||
|
||||
// Set callback to format the column (hack column definition to ensure callbacks are executed).
|
||||
$instance = manager::get_report_from_persistent($report);
|
||||
$instance->get_column('user:suspended')
|
||||
->set_type(column::TYPE_FLOAT)
|
||||
->set_callback(static function(float $value): string {
|
||||
return number_format($value, 1) . ' suspended';
|
||||
});
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertEquals([
|
||||
[
|
||||
'c0_firstname' => 'Admin',
|
||||
'c1_suspended' => '0.0 suspended',
|
||||
],
|
||||
[
|
||||
'c0_firstname' => 'Bob',
|
||||
'c1_suspended' => '0.5 suspended',
|
||||
],
|
||||
], $content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace core_reportbuilder\local\aggregation;
|
|||
|
||||
use core_reportbuilder_testcase;
|
||||
use core_reportbuilder_generator;
|
||||
use core_reportbuilder\manager;
|
||||
use core_reportbuilder\local\report\column;
|
||||
use core_user\reportbuilder\datasource\users;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
@ -74,4 +76,49 @@ class sum_test extends core_reportbuilder_testcase {
|
|||
],
|
||||
], $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test aggregation when applied to column with callback
|
||||
*/
|
||||
public function test_column_aggregation_with_callback(): 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();
|
||||
|
||||
// Set callback to format the column (hack column definition to ensure callbacks are executed).
|
||||
$instance = manager::get_report_from_persistent($report);
|
||||
$instance->get_column('user:suspended')
|
||||
->set_type(column::TYPE_INTEGER)
|
||||
->set_callback(static function(int $value): string {
|
||||
return "{$value} suspended";
|
||||
});
|
||||
|
||||
$content = $this->get_custom_report_content($report->get('id'));
|
||||
$this->assertEquals([
|
||||
[
|
||||
'c0_firstname' => 'Admin',
|
||||
'c1_suspended' => '0 suspended',
|
||||
],
|
||||
[
|
||||
'c0_firstname' => 'Bob',
|
||||
'c1_suspended' => '2 suspended',
|
||||
],
|
||||
], $content);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue