Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 403]

   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  declare(strict_types=1);
  18  
  19  use core_reportbuilder\manager;
  20  use core_reportbuilder\local\helpers\aggregation;
  21  use core_reportbuilder\local\helpers\report;
  22  use core_reportbuilder\local\helpers\user_filter_manager;
  23  use core_reportbuilder\table\custom_report_table_view;
  24  
  25  /**
  26   * Helper base class for reportbuilder unit tests
  27   *
  28   * @package     core_reportbuilder
  29   * @copyright   2021 Paul Holden <paulh@moodle.com>
  30   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31   */
  32  abstract class core_reportbuilder_testcase extends advanced_testcase {
  33  
  34      /**
  35       * Retrieve content for given report as array of report data
  36       *
  37       * @param int $reportid
  38       * @param int $pagesize
  39       * @param array $filtervalues
  40       * @return array[]
  41       */
  42      protected function get_custom_report_content(int $reportid, int $pagesize = 30, array $filtervalues = []): array {
  43          $records = [];
  44  
  45          // Apply filter values.
  46          user_filter_manager::set($reportid, $filtervalues);
  47  
  48          // Create table instance.
  49          $table = custom_report_table_view::create($reportid);
  50          $table->setup();
  51          $table->query_db($pagesize, false);
  52  
  53          // Extract raw data.
  54          foreach ($table->rawdata as $record) {
  55              $records[] = $table->format_row($record);
  56          }
  57  
  58          $table->close_recordset();
  59  
  60          return $records;
  61      }
  62  
  63      /**
  64       * Stress test a report source by iterating over all it's columns, enabling sorting where possible and asserting we can
  65       * create a report for each
  66       *
  67       * @param string $source
  68       */
  69      protected function datasource_stress_test_columns(string $source): void {
  70  
  71          /** @var core_reportbuilder_generator $generator */
  72          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
  73  
  74          $report = $generator->create_report(['name' => 'Stress columns', 'source' => $source, 'default' => 0]);
  75          $instance = manager::get_report_from_persistent($report);
  76  
  77          // Iterate over each available column, ensure each works correctly independent of any others.
  78          foreach ($instance->get_columns() as $columnidentifier => $columninstance) {
  79              $column = report::add_report_column($report->get('id'), $columnidentifier);
  80  
  81              // Enable sorting of the column where possible.
  82              if ($columninstance->get_is_sortable()) {
  83                  report::toggle_report_column_sorting($report->get('id'), $column->get('id'), true, SORT_DESC);
  84              }
  85  
  86              // We are only asserting the report returns content without errors, not the content itself.
  87              try {
  88                  $content = $this->get_custom_report_content($report->get('id'));
  89                  $this->assertNotEmpty($content);
  90              } catch (Throwable $exception) {
  91                  $this->fail("Error for column '{$columnidentifier}': " . $exception->getMessage());
  92              }
  93  
  94              report::delete_report_column($report->get('id'), $column->get('id'));
  95          }
  96      }
  97  
  98      /**
  99       * Stress test a report source by iterating over all columns and asserting we can create a report while aggregating each
 100       *
 101       * @param string $source
 102       */
 103      protected function datasource_stress_test_columns_aggregation(string $source): void {
 104  
 105          /** @var core_reportbuilder_generator $generator */
 106          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 107  
 108          $report = $generator->create_report(['name' => 'Stress aggregation', 'source' => $source, 'default' => 0]);
 109          $instance = manager::get_report_from_persistent($report);
 110  
 111          // Add every column.
 112          $columnidentifiers = array_keys($instance->get_columns());
 113          foreach ($columnidentifiers as $columnidentifier) {
 114              report::add_report_column($report->get('id'), $columnidentifier);
 115          }
 116  
 117          // Now iterate over each column, and apply all suitable aggregation types.
 118          foreach ($instance->get_active_columns() as $column) {
 119              $aggregations = aggregation::get_column_aggregations($column->get_type(), $column->get_disabled_aggregation());
 120              foreach (array_keys($aggregations) as $aggregation) {
 121                  $column->get_persistent()->set('aggregation', $aggregation)->update();
 122  
 123                  // We are only asserting the report returns content without errors, not the content itself.
 124                  try {
 125                      $content = $this->get_custom_report_content($report->get('id'));
 126                      $this->assertNotEmpty($content);
 127                  } catch (Throwable $exception) {
 128                      $this->fail("Error for column '{$column->get_unique_identifier()}' with aggregation '{$aggregation}': " .
 129                          $exception->getMessage());
 130                  }
 131              }
 132  
 133              // Reset the column aggregation.
 134              $column->get_persistent()->set('aggregation', null)->update();
 135          }
 136      }
 137  
 138      /**
 139       * Stress test a report source by iterating over all it's conditions and asserting we can create a report using each
 140       *
 141       * @param string $source
 142       * @param string $columnidentifier Should be a simple column, with as few fields and joins as possible, ideally selected
 143       *      from the base table itself
 144       */
 145      protected function datasource_stress_test_conditions(string $source, string $columnidentifier): void {
 146  
 147          /** @var core_reportbuilder_generator $generator */
 148          $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
 149  
 150          $report = $generator->create_report(['name' => 'Stress conditions', 'source' => $source, 'default' => 0]);
 151          $instance = manager::get_report_from_persistent($report);
 152  
 153          // Add single column only (to ensure no conditions have reliance on any columns).
 154          report::add_report_column($report->get('id'), $columnidentifier);
 155  
 156          // Iterate over each available condition, ensure each works correctly independent of any others.
 157          $conditionidentifiers = array_keys($instance->get_conditions());
 158          foreach ($conditionidentifiers as $conditionidentifier) {
 159              $condition = report::add_report_condition($report->get('id'), $conditionidentifier);
 160              $conditioninstance = $instance->get_condition($condition->get('uniqueidentifier'));
 161  
 162              /** @var \core_reportbuilder\local\filters\base $conditionclass */
 163              $conditionclass = $conditioninstance->get_filter_class();
 164  
 165              // Set report condition values in order to activate it.
 166              $conditionvalues = $conditionclass::create($conditioninstance)->get_sample_values();
 167              if (empty($conditionvalues)) {
 168                  debugging("Missing sample values from filter '{$conditionclass}'", DEBUG_DEVELOPER);
 169              }
 170              $instance->set_condition_values($conditionvalues);
 171  
 172              // We are only asserting the report returns content without errors, not the content itself.
 173              try {
 174                  $content = $this->get_custom_report_content($report->get('id'));
 175                  $this->assertIsArray($content);
 176              } catch (Throwable $exception) {
 177                  $this->fail("Error for condition '{$conditionidentifier}': " . $exception->getMessage());
 178              }
 179  
 180              report::delete_report_condition($report->get('id'), $condition->get('id'));
 181          }
 182      }
 183  }