1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 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. 8 // 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. 13 // 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/>. 16 17 namespace core_admin\reportbuilder\local\entities; 18 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; 26 use lang_string; 27 use core_reportbuilder\local\entities\base; 28 use core_reportbuilder\local\report\column; 29 use core_reportbuilder\local\report\filter; 30 use stdClass; 31 use core_collator; 32 33 /** 34 * Task log entity class implementation 35 * 36 * @package core_admin 37 * @copyright 2021 David Matamoros <davidmc@moodle.com> 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class task_log extends base { 41 42 /** @var int Result success */ 43 protected const SUCCESS = 0; 44 45 /** @var int Result failed */ 46 protected const FAILED = 1; 47 48 /** 49 * Database tables that this entity uses and their default aliases 50 * 51 * @return array 52 */ 53 protected function get_default_table_aliases(): array { 54 return ['task_log' => 'tl']; 55 } 56 57 /** 58 * The default title for this entity in the list of columns/conditions/filters in the report builder 59 * 60 * @return lang_string 61 */ 62 protected function get_default_entity_title(): lang_string { 63 return new lang_string('entitytasklog', 'admin'); 64 } 65 66 /** 67 * Initialise the entity 68 * 69 * @return base 70 */ 71 public function initialise(): base { 72 $columns = $this->get_all_columns(); 73 foreach ($columns as $column) { 74 $this->add_column($column); 75 } 76 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) { 80 $this 81 ->add_filter($filter) 82 ->add_condition($filter); 83 } 84 85 return $this; 86 } 87 88 /** 89 * Returns list of all available columns 90 * 91 * @return column[] 92 */ 93 protected function get_all_columns(): array { 94 global $DB; 95 96 $tablealias = $this->get_table_alias('task_log'); 97 98 // Name column. 99 $columns[] = (new column( 100 'name', 101 new lang_string('name'), 102 $this->get_entity_name() 103 )) 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 { 109 $output = ''; 110 if (class_exists($classname)) { 111 $task = new $classname; 112 if ($task instanceof \core\task\task_base) { 113 $output = $task->get_name(); 114 } 115 } 116 $output .= \html_writer::tag('div', "\\{$classname}", [ 117 'class' => 'small text-muted', 118 ]); 119 return $output; 120 }); 121 122 // Component column. 123 $columns[] = (new column( 124 'component', 125 new lang_string('plugin'), 126 $this->get_entity_name() 127 )) 128 ->add_joins($this->get_joins()) 129 ->set_type(column::TYPE_TEXT) 130 ->add_field("{$tablealias}.component") 131 ->set_is_sortable(true); 132 133 // Type column. 134 $columns[] = (new column( 135 'type', 136 new lang_string('tasktype', 'admin'), 137 $this->get_entity_name() 138 )) 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'); 146 } 147 return get_string('task_type:adhoc', 'admin'); 148 }); 149 150 // Start time column. 151 $columns[] = (new column( 152 'starttime', 153 new lang_string('task_starttime', 'admin'), 154 $this->get_entity_name() 155 )) 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')); 161 162 // End time column. 163 $columns[] = (new column( 164 'endtime', 165 new lang_string('task_endtime', 'admin'), 166 $this->get_entity_name() 167 )) 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')); 173 174 // Duration column. 175 $columns[] = (new column( 176 'duration', 177 new lang_string('task_duration', 'admin'), 178 $this->get_entity_name() 179 )) 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'); 190 } 191 return format_time($duration); 192 }); 193 194 // Hostname column. 195 $columns[] = (new column( 196 'hostname', 197 new lang_string('hostname', 'admin'), 198 $this->get_entity_name() 199 )) 200 ->add_joins($this->get_joins()) 201 ->set_type(column::TYPE_TEXT) 202 ->add_field("$tablealias.hostname") 203 ->set_is_sortable(true); 204 205 // PID column. 206 $columns[] = (new column( 207 'pid', 208 new lang_string('pid', 'admin'), 209 $this->get_entity_name() 210 )) 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']); 217 218 // Database column. 219 $columns[] = (new column( 220 'database', 221 new lang_string('task_dbstats', 'admin'), 222 $this->get_entity_name() 223 )) 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 { 229 $output = ''; 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)); 232 return $output; 233 }) 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']); 236 237 // Database reads column. 238 $columns[] = (new column( 239 'dbreads', 240 new lang_string('task_dbreads', 'admin'), 241 $this->get_entity_name() 242 )) 243 ->add_joins($this->get_joins()) 244 ->set_type(column::TYPE_INTEGER) 245 ->add_fields("{$tablealias}.dbreads") 246 ->set_is_sortable(true); 247 248 // Database writes column. 249 $columns[] = (new column( 250 'dbwrites', 251 new lang_string('task_dbwrites', 'admin'), 252 $this->get_entity_name() 253 )) 254 ->add_joins($this->get_joins()) 255 ->set_type(column::TYPE_INTEGER) 256 ->add_fields("{$tablealias}.dbwrites") 257 ->set_is_sortable(true); 258 259 // Result column. 260 $columns[] = (new column( 261 'result', 262 new lang_string('task_result', 'admin'), 263 $this->get_entity_name() 264 )) 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 { 271 if (!$success) { 272 return get_string('task_result:failed', 'admin'); 273 } 274 return get_string('success'); 275 }); 276 277 return $columns; 278 } 279 280 /** 281 * Return list of all available filters 282 * 283 * @return filter[] 284 */ 285 protected function get_all_filters(): array { 286 global $DB; 287 288 $tablealias = $this->get_table_alias('task_log'); 289 290 // Name filter (Filter by classname). 291 $filters[] = (new filter( 292 autocomplete::class, 293 'name', 294 new lang_string('classname', 'tool_task'), 295 $this->get_entity_name(), 296 "{$tablealias}.classname" 297 )) 298 ->add_joins($this->get_joins()) 299 ->set_options_callback(static function(): array { 300 global $DB; 301 $classnames = $DB->get_fieldset_sql('SELECT DISTINCT classname FROM {task_log} ORDER BY classname ASC'); 302 303 $options = []; 304 foreach ($classnames as $classname) { 305 if (class_exists($classname)) { 306 $task = new $classname; 307 $options[$classname] = $task->get_name(); 308 } 309 } 310 311 core_collator::asort($options); 312 return $options; 313 }); 314 315 // Component filter. 316 $filters[] = (new filter( 317 text::class, 318 'component', 319 new lang_string('plugin'), 320 $this->get_entity_name(), 321 "{$tablealias}.component" 322 )) 323 ->add_joins($this->get_joins()); 324 325 // Type filter. 326 $filters[] = (new filter( 327 select::class, 328 'type', 329 new lang_string('tasktype', 'admin'), 330 $this->get_entity_name(), 331 "{$tablealias}.type" 332 )) 333 ->add_joins($this->get_joins()) 334 ->set_options([ 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'), 337 ]); 338 339 // Output filter (Filter by task output). 340 $filters[] = (new filter( 341 text::class, 342 'output', 343 new lang_string('task_logoutput', 'admin'), 344 $this->get_entity_name(), 345 $DB->sql_cast_to_char("{$tablealias}.output") 346 )) 347 ->add_joins($this->get_joins()); 348 349 // Start time filter. 350 $filters[] = (new filter( 351 date::class, 352 'timestart', 353 new lang_string('task_starttime', 'admin'), 354 $this->get_entity_name(), 355 "{$tablealias}.timestart" 356 )) 357 ->add_joins($this->get_joins()) 358 ->set_limited_operators([ 359 date::DATE_ANY, 360 date::DATE_RANGE, 361 date::DATE_PREVIOUS, 362 date::DATE_CURRENT, 363 ]); 364 365 // End time. 366 $filters[] = (new filter( 367 date::class, 368 'timeend', 369 new lang_string('task_endtime', 'admin'), 370 $this->get_entity_name(), 371 "{$tablealias}.timeend" 372 )) 373 ->add_joins($this->get_joins()) 374 ->set_limited_operators([ 375 date::DATE_ANY, 376 date::DATE_RANGE, 377 date::DATE_PREVIOUS, 378 date::DATE_CURRENT, 379 ]); 380 381 // Duration filter. 382 $filters[] = (new filter( 383 duration::class, 384 'duration', 385 new lang_string('task_duration', 'admin'), 386 $this->get_entity_name(), 387 "{$tablealias}.timeend - {$tablealias}.timestart" 388 )) 389 ->add_joins($this->get_joins()); 390 391 // Database reads. 392 $filters[] = (new filter( 393 number::class, 394 'dbreads', 395 new lang_string('task_dbreads', 'admin'), 396 $this->get_entity_name(), 397 "{$tablealias}.dbreads" 398 )) 399 ->add_joins($this->get_joins()); 400 401 // Database writes. 402 $filters[] = (new filter( 403 number::class, 404 'dbwrites', 405 new lang_string('task_dbwrites', 'admin'), 406 $this->get_entity_name(), 407 "{$tablealias}.dbwrites" 408 )) 409 ->add_joins($this->get_joins()); 410 411 // Result filter. 412 $filters[] = (new filter( 413 select::class, 414 'result', 415 new lang_string('task_result', 'admin'), 416 $this->get_entity_name(), 417 "{$tablealias}.result" 418 )) 419 ->add_joins($this->get_joins()) 420 ->set_options([ 421 self::SUCCESS => get_string('success'), 422 self::FAILED => get_string('task_result:failed', 'admin'), 423 ]); 424 425 return $filters; 426 } 427 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body