2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 namespace core_admin\local\entities
;
19 use core_reportbuilder\local\filters\date
;
20 use core_reportbuilder\local\filters\duration
;
21 use core_reportbuilder\local\filters\number
;
22 use core_reportbuilder\local\filters\select
;
23 use core_reportbuilder\local\filters\text
;
24 use core_reportbuilder\local\filters\autocomplete
;
25 use core_reportbuilder\local\helpers\format
;
27 use core_reportbuilder\local\entities\base
;
28 use core_reportbuilder\local\report\column
;
29 use core_reportbuilder\local\report\filter
;
34 * Task log entity class implementation
37 * @copyright 2021 David Matamoros <davidmc@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class task_log
extends base
{
42 /** @var int Result success */
43 protected const SUCCESS
= 0;
45 /** @var int Result failed */
46 protected const FAILED
= 1;
49 * Database tables that this entity uses and their default aliases
53 protected function get_default_table_aliases(): array {
54 return ['task_log' => 'tl'];
58 * The default title for this entity in the list of columns/conditions/filters in the report builder
62 protected function get_default_entity_title(): lang_string
{
63 return new lang_string('entitytasklog', 'admin');
67 * Initialise the entity
71 public function initialise(): base
{
72 $columns = $this->get_all_columns();
73 foreach ($columns as $column) {
74 $this->add_column($column);
77 // All the filters defined by the entity can also be used as conditions.
78 $filters = $this->get_all_filters();
79 foreach ($filters as $filter) {
82 ->add_condition($filter);
89 * Returns list of all available columns
93 protected function get_all_columns(): array {
96 $tablealias = $this->get_table_alias('task_log');
99 $columns[] = (new column(
101 new lang_string('name'),
102 $this->get_entity_name()
104 ->add_joins($this->get_joins())
105 ->set_type(column
::TYPE_TEXT
)
106 ->add_field("$tablealias.classname")
107 ->set_is_sortable(true)
108 ->add_callback(static function(string $classname): string {
110 if (class_exists($classname)) {
111 $task = new $classname;
112 if ($task instanceof \core\task\task_base
) {
113 $output = $task->get_name();
116 $output .= \html_writer
::tag('div', "\\{$classname}", [
117 'class' => 'small text-muted',
123 $columns[] = (new column(
125 new lang_string('plugin'),
126 $this->get_entity_name()
128 ->add_joins($this->get_joins())
129 ->set_type(column
::TYPE_TEXT
)
130 ->add_field("{$tablealias}.component")
131 ->set_is_sortable(true);
134 $columns[] = (new column(
136 new lang_string('tasktype', 'admin'),
137 $this->get_entity_name()
139 ->add_joins($this->get_joins())
140 ->set_type(column
::TYPE_TEXT
)
141 ->add_field("{$tablealias}.type")
142 ->set_is_sortable(true)
143 ->add_callback(static function($value): string {
144 if (\core\task\database_logger
::TYPE_SCHEDULED
=== (int) $value) {
145 return get_string('task_type:scheduled', 'admin');
147 return get_string('task_type:adhoc', 'admin');
150 // Start time column.
151 $columns[] = (new column(
153 new lang_string('task_starttime', 'admin'),
154 $this->get_entity_name()
156 ->add_joins($this->get_joins())
157 ->set_type(column
::TYPE_TIMESTAMP
)
158 ->add_field("{$tablealias}.timestart")
159 ->set_is_sortable(true)
160 ->add_callback([format
::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
163 $columns[] = (new column(
165 new lang_string('task_endtime', 'admin'),
166 $this->get_entity_name()
168 ->add_joins($this->get_joins())
169 ->set_type(column
::TYPE_TIMESTAMP
)
170 ->add_field("{$tablealias}.timeend")
171 ->set_is_sortable(true)
172 ->add_callback([format
::class, 'userdate'], get_string('strftimedatetimeshortaccurate', 'core_langconfig'));
175 $columns[] = (new column(
177 new lang_string('task_duration', 'admin'),
178 $this->get_entity_name()
180 ->add_joins($this->get_joins())
181 ->set_type(column
::TYPE_FLOAT
)
182 ->add_field("{$tablealias}.timeend - {$tablealias}.timestart", 'duration')
183 ->set_is_sortable(true)
184 ->add_callback(static function(float $value): string {
185 $duration = round($value, 2);
186 if (empty($duration)) {
187 // The format_time function returns 'now' when the difference is exactly 0.
188 // Note: format_time performs concatenation in exactly this fashion so we should do this for consistency.
189 return '0 ' . get_string('secs', 'moodle');
191 return format_time($duration);
195 $columns[] = (new column(
197 new lang_string('hostname', 'admin'),
198 $this->get_entity_name()
200 ->add_joins($this->get_joins())
201 ->set_type(column
::TYPE_TEXT
)
202 ->add_field("$tablealias.hostname")
203 ->set_is_sortable(true);
206 $columns[] = (new column(
208 new lang_string('pid', 'admin'),
209 $this->get_entity_name()
211 ->add_joins($this->get_joins())
212 ->set_type(column
::TYPE_INTEGER
)
213 ->add_field("{$tablealias}.pid")
214 ->set_is_sortable(true)
215 // Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
216 ->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
219 $columns[] = (new column(
221 new lang_string('task_dbstats', 'admin'),
222 $this->get_entity_name()
224 ->add_joins($this->get_joins())
225 ->set_type(column
::TYPE_INTEGER
)
226 ->add_fields("{$tablealias}.dbreads, {$tablealias}.dbwrites")
227 ->set_is_sortable(true, ["{$tablealias}.dbreads", "{$tablealias}.dbwrites"])
228 ->add_callback(static function(int $value, stdClass
$row): string {
230 $output .= \html_writer
::div(get_string('task_stats:dbreads', 'admin', $row->dbreads
));
231 $output .= \html_writer
::div(get_string('task_stats:dbwrites', 'admin', $row->dbwrites
));
234 // Although this is an integer column, it doesn't make sense to perform numeric aggregation on it.
235 ->set_disabled_aggregation(['avg', 'count', 'countdistinct', 'max', 'min', 'sum']);
237 // Database reads column.
238 $columns[] = (new column(
240 new lang_string('task_dbreads', 'admin'),
241 $this->get_entity_name()
243 ->add_joins($this->get_joins())
244 ->set_type(column
::TYPE_INTEGER
)
245 ->add_fields("{$tablealias}.dbreads")
246 ->set_is_sortable(true);
248 // Database writes column.
249 $columns[] = (new column(
251 new lang_string('task_dbwrites', 'admin'),
252 $this->get_entity_name()
254 ->add_joins($this->get_joins())
255 ->set_type(column
::TYPE_INTEGER
)
256 ->add_fields("{$tablealias}.dbwrites")
257 ->set_is_sortable(true);
260 $columns[] = (new column(
262 new lang_string('task_result', 'admin'),
263 $this->get_entity_name()
265 ->add_joins($this->get_joins())
266 ->set_type(column
::TYPE_BOOLEAN
)
267 // For accurate aggregation, we need to return boolean success = true by xor'ing the field value.
268 ->add_field($DB->sql_bitxor("{$tablealias}.result", 1), 'success')
269 ->set_is_sortable(true)
270 ->add_callback(static function(bool $success): string {
272 return get_string('task_result:failed', 'admin');
274 return get_string('success');
281 * Return list of all available filters
285 protected function get_all_filters(): array {
288 $tablealias = $this->get_table_alias('task_log');
290 // Name filter (Filter by classname).
291 $filters[] = (new filter(
294 new lang_string('classname', 'tool_task'),
295 $this->get_entity_name(),
296 "{$tablealias}.classname"
298 ->add_joins($this->get_joins())
299 ->set_options_callback(static function(): array {
301 $classnames = $DB->get_fieldset_sql('SELECT DISTINCT classname FROM {task_log} ORDER BY classname ASC');
304 foreach ($classnames as $classname) {
305 if (class_exists($classname)) {
306 $task = new $classname;
307 $options[$classname] = $task->get_name();
311 core_collator
::asort($options);
316 $filters[] = (new filter(
319 new lang_string('plugin'),
320 $this->get_entity_name(),
321 "{$tablealias}.component"
323 ->add_joins($this->get_joins());
326 $filters[] = (new filter(
329 new lang_string('tasktype', 'admin'),
330 $this->get_entity_name(),
333 ->add_joins($this->get_joins())
335 \core\task\database_logger
::TYPE_ADHOC
=> new lang_string('task_type:adhoc', 'admin'),
336 \core\task\database_logger
::TYPE_SCHEDULED
=> new lang_string('task_type:scheduled', 'admin'),
339 // Output filter (Filter by task output).
340 $filters[] = (new filter(
343 new lang_string('task_logoutput', 'admin'),
344 $this->get_entity_name(),
345 "{$tablealias}.output"
347 ->add_joins($this->get_joins());
349 // Start time filter.
350 $filters[] = (new filter(
353 new lang_string('task_starttime', 'admin'),
354 $this->get_entity_name(),
355 "{$tablealias}.timestart"
357 ->add_joins($this->get_joins())
358 ->set_limited_operators([
366 $filters[] = (new filter(
369 new lang_string('task_endtime', 'admin'),
370 $this->get_entity_name(),
371 "{$tablealias}.timeend"
373 ->add_joins($this->get_joins())
374 ->set_limited_operators([
382 $filters[] = (new filter(
385 new lang_string('task_duration', 'admin'),
386 $this->get_entity_name(),
387 "{$tablealias}.timeend - {$tablealias}.timestart"
389 ->add_joins($this->get_joins());
392 $filters[] = (new filter(
395 new lang_string('task_dbreads', 'admin'),
396 $this->get_entity_name(),
397 "{$tablealias}.dbreads"
399 ->add_joins($this->get_joins());
402 $filters[] = (new filter(
405 new lang_string('task_dbwrites', 'admin'),
406 $this->get_entity_name(),
407 "{$tablealias}.dbwrites"
409 ->add_joins($this->get_joins());
412 $filters[] = (new filter(
415 new lang_string('task_result', 'admin'),
416 $this->get_entity_name(),
417 "{$tablealias}.result"
419 ->add_joins($this->get_joins())
421 self
::SUCCESS
=> get_string('success'),
422 self
::FAILED
=> get_string('task_result:failed', 'admin'),