Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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