MDL-70795 reportbuilder: define updated schema for custom reports.

Define new filter/column models, and updated database schema to
match.
This commit is contained in:
Paul Holden 2021-07-05 11:51:20 +01:00
parent 8885e22a0b
commit 95967d62d5
9 changed files with 453 additions and 6 deletions

View File

@ -66,6 +66,14 @@ $string['filtersapplied'] = 'Filters applied';
$string['filtersappliedx'] = 'Filters ({$a})';
$string['filtersreset'] = 'Filters reset';
$string['filterstartswith'] = 'Starts with';
$string['privacy:metadata:column'] = 'Report column definitions';
$string['privacy:metadata:column:uniqueidentifier'] = 'Unique identifier of the column';
$string['privacy:metadata:column:usercreated'] = 'The ID of the user who created the column';
$string['privacy:metadata:column:usermodified'] = 'The ID of the user who last modified the column';
$string['privacy:metadata:filter'] = 'Report filter definitions';
$string['privacy:metadata:filter:uniqueidentifier'] = 'Unique identifier of the filter';
$string['privacy:metadata:filter:usercreated'] = 'The ID of the user who created the filter';
$string['privacy:metadata:filter:usermodified'] = 'The ID of the user who last modified the filter';
$string['privacy:metadata:preference:reportfilter'] = 'Stored report filter values';
$string['privacy:metadata:report'] = 'Report definitions';
$string['privacy:metadata:report:name'] = 'The name of the report';

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20211005" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20211008" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -4382,8 +4382,10 @@
<TABLE NAME="reportbuilder_report" COMMENT="Table to represent a report">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="source" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="type" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="conditiondata" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="area" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
@ -4400,5 +4402,48 @@
<KEY NAME="contextid" TYPE="foreign" FIELDS="contextid" REFTABLE="context" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="reportbuilder_column" COMMENT="Table to represent a report column">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="reportid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="uniqueidentifier" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="aggregation" TYPE="char" LENGTH="32" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="heading" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="columnorder" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="sortenabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="sortdirection" TYPE="int" LENGTH="1" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="usercreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="reportid" TYPE="foreign" FIELDS="reportid" REFTABLE="reportbuilder_report" REFFIELDS="id"/>
<KEY NAME="usercreated" TYPE="foreign" FIELDS="usercreated" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="reportbuilder_filter" COMMENT="Table to represent a report filter/condition">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="reportid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="uniqueidentifier" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="heading" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="iscondition" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="filterorder" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="usercreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="reportid" TYPE="foreign" FIELDS="reportid" REFTABLE="reportbuilder_report" REFFIELDS="id"/>
<KEY NAME="usercreated" TYPE="foreign" FIELDS="usercreated" REFTABLE="user" REFFIELDS="id"/>
<KEY NAME="usermodified" TYPE="foreign" FIELDS="usermodified" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View File

@ -2936,5 +2936,79 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2021100600.04);
}
if ($oldversion < 2021101900.01) {
$table = new xmldb_table('reportbuilder_report');
// Define field name to be added to reportbuilder_report.
$field = new xmldb_field('name', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'id');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define field conditiondata to be added to reportbuilder_report.
$field = new xmldb_field('conditiondata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'type');
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define table reportbuilder_column to be created.
$table = new xmldb_table('reportbuilder_column');
// Adding fields to table reportbuilder_column.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('aggregation', XMLDB_TYPE_CHAR, '32', null, null, null, null);
$table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('columnorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('sortenabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('sortdirection', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, null);
$table->add_field('sortorder', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
$table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table reportbuilder_column.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
$table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
$table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
// Conditionally launch create table for reportbuilder_column.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Define table reportbuilder_filter to be created.
$table = new xmldb_table('reportbuilder_filter');
// Adding fields to table reportbuilder_filter.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('reportid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('uniqueidentifier', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('heading', XMLDB_TYPE_CHAR, '255', null, null, null, null);
$table->add_field('iscondition', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('filterorder', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('usercreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table reportbuilder_filter.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('reportid', XMLDB_KEY_FOREIGN, ['reportid'], 'reportbuilder_report', ['id']);
$table->add_key('usercreated', XMLDB_KEY_FOREIGN, ['usercreated'], 'user', ['id']);
$table->add_key('usermodified', XMLDB_KEY_FOREIGN, ['usermodified'], 'user', ['id']);
// Conditionally launch create table for reportbuilder_filter.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2021101900.01);
}
return true;
}

View File

@ -0,0 +1,122 @@
<?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_reportbuilder\local\models;
use lang_string;
use core\persistent;
/**
* Persistent class to represent a report column
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column extends persistent {
/** @var string The table name. */
public const TABLE = 'reportbuilder_column';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'uniqueidentifier' => [
'type' => PARAM_RAW,
],
'aggregation' => [
'type' => PARAM_ALPHANUMEXT,
'default' => null,
'null' => NULL_ALLOWED,
],
'heading' => [
'type' => PARAM_TEXT,
'default' => null,
'null' => NULL_ALLOWED,
],
'columnorder' => [
'type' => PARAM_INT,
],
'sortenabled' => [
'type' => PARAM_BOOL,
'default' => false,
],
'sortdirection' => [
'type' => PARAM_INT,
'choices' => [SORT_ASC, SORT_DESC],
'default' => SORT_ASC,
],
'sortorder' => [
'type' => PARAM_INT,
'default' => null,
'null' => NULL_ALLOWED,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Return the report this column belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Helper method to return the current maximum column order value for a report
*
* @param int $reportid
* @param string $columnname
* @return int
*/
public static function get_max_columnorder(int $reportid, string $columnname): int {
global $DB;
return (int) $DB->get_field(static::TABLE, "MAX({$columnname})", ['reportid' => $reportid], MUST_EXIST);
}
}

View File

@ -0,0 +1,155 @@
<?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_reportbuilder\local\models;
use lang_string;
use core\persistent;
/**
* Persistent class to represent a report filter/condition
*
* @package core_reportbuilder
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filter extends persistent {
/** @var string The table name. */
public const TABLE = 'reportbuilder_filter';
/**
* Return the definition of the properties of this model.
*
* @return array
*/
protected static function define_properties(): array {
return [
'reportid' => [
'type' => PARAM_INT,
],
'uniqueidentifier' => [
'type' => PARAM_RAW,
],
'heading' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'iscondition' => [
'type' => PARAM_BOOL,
'default' => false,
],
'filterorder' => [
'type' => PARAM_INT,
],
'usercreated' => [
'type' => PARAM_INT,
'default' => static function(): int {
global $USER;
return (int) $USER->id;
},
],
];
}
/**
* Validate reportid property
*
* @param int $reportid
* @return bool|lang_string
*/
protected function validate_reportid(int $reportid) {
if (!report::record_exists($reportid)) {
return new lang_string('invaliddata', 'error');
}
return true;
}
/**
* Return the report this filter belongs to
*
* @return report
*/
public function get_report(): report {
return new report($this->get('reportid'));
}
/**
* Return filter record
*
* @param int $reportid
* @param int $filterid
* @return false|static
*/
public static function get_filter_record(int $reportid, int $filterid) {
return self::get_record(['id' => $filterid, 'reportid' => $reportid, 'iscondition' => 0]);
}
/**
* Return filter records for report
*
* @param int $reportid
* @param string $sort
* @param string $order
* @return static[]
*/
public static function get_filter_records(int $reportid, string $sort = '', string $order = 'ASC'): array {
return self::get_records(['reportid' => $reportid, 'iscondition' => 0], $sort, $order);
}
/**
* Return condition record
*
* @param int $reportid
* @param int $conditionid
* @return false|static
*/
public static function get_condition_record(int $reportid, int $conditionid) {
return self::get_record(['id' => $conditionid, 'reportid' => $reportid, 'iscondition' => 1]);
}
/**
* Return condition records for report
*
* @param int $reportid
* @param string $sort
* @param string $order
* @return static[]
*/
public static function get_condition_records(int $reportid, string $sort = '', string $order = 'ASC'): array {
return self::get_records(['reportid' => $reportid, 'iscondition' => 1], $sort, $order);
}
/**
* Helper method to return the current maximum filter order value for a report
*
* @param int $reportid
* @param bool $iscondition
* @return int
*/
public static function get_max_filterorder(int $reportid, bool $iscondition = false): int {
global $DB;
$params = ['reportid' => $reportid, 'iscondition' => (int) $iscondition];
return (int) $DB->get_field(static::TABLE, "MAX(filterorder)", $params, MUST_EXIST);
}
}

View File

@ -42,6 +42,11 @@ class report extends persistent {
*/
protected static function define_properties(): array {
return [
'name' => [
'type' => PARAM_TEXT,
'null' => NULL_ALLOWED,
'default' => null,
],
'source' => [
'type' => PARAM_RAW,
],
@ -52,6 +57,11 @@ class report extends persistent {
base::TYPE_SYSTEM_REPORT,
],
],
'conditiondata' => [
'type' => PARAM_RAW,
'null' => NULL_ALLOWED,
'default' => null,
],
'contextid' => [
'type' => PARAM_INT,
'default' => static function(): int {
@ -89,4 +99,13 @@ class report extends persistent {
public function get_context(): context {
return context::instance_by_id($this->raw_get('contextid'));
}
/**
* Return formatted report name
*
* @return string
*/
public function get_formatted_name(): string {
return format_string($this->raw_get('name'), true, ['context' => $this->get_context(), 'escape' => true]);
}
}

View File

@ -21,7 +21,9 @@ namespace core_reportbuilder\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\report;
use core_reportbuilder\local\models\filter;
/**
* Privacy Subsystem for core_reportbuilder
@ -47,6 +49,18 @@ class provider implements
'usermodified' => 'privacy:metadata:report:usermodified',
], 'privacy:metadata:report');
$collection->add_database_table(column::TABLE, [
'uniqueidentifier' => 'privacy:metadata:column:uniqueidentifier',
'usercreated' => 'privacy:metadata:column:usercreated',
'usermodified' => 'privacy:metadata:column:usermodified',
], 'privacy:metadata:column');
$collection->add_database_table(filter::TABLE, [
'uniqueidentifier' => 'privacy:metadata:filter:uniqueidentifier',
'usercreated' => 'privacy:metadata:filter:usercreated',
'usermodified' => 'privacy:metadata:filter:usermodified',
], 'privacy:metadata:filter');
$collection->add_user_preference('core_reportbuilder', 'privacy:metadata:preference:reportfilter');
return $collection;

View File

@ -23,8 +23,12 @@ use core_privacy\local\metadata\collection;
use core_privacy\local\metadata\types\database_table;
use core_privacy\local\metadata\types\user_preference;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
use core_reportbuilder\manager;
use core_reportbuilder\local\helpers\user_filter_manager;
use core_reportbuilder\local\models\column;
use core_reportbuilder\local\models\filter;
use core_reportbuilder\local\models\report;
/**
* Unit tests for privacy provider
@ -34,7 +38,7 @@ use core_reportbuilder\local\helpers\user_filter_manager;
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_testcase extends \core_privacy\tests\provider_testcase {
class provider_test extends provider_testcase {
/**
* Test provider metadata
@ -43,12 +47,18 @@ class provider_testcase extends \core_privacy\tests\provider_testcase {
$collection = new collection('core_reportbuilder');
$metadata = provider::get_metadata($collection)->get_collection();
$this->assertCount(2, $metadata);
$this->assertCount(4, $metadata);
$this->assertInstanceOf(database_table::class, $metadata[0]);
$this->assertEquals('reportbuilder_report', $metadata[0]->get_name());
$this->assertEquals(report::TABLE, $metadata[0]->get_name());
$this->assertInstanceOf(user_preference::class, $metadata[1]);
$this->assertInstanceOf(database_table::class, $metadata[1]);
$this->assertEquals(column::TABLE, $metadata[1]->get_name());
$this->assertInstanceOf(database_table::class, $metadata[2]);
$this->assertEquals(filter::TABLE, $metadata[2]->get_name());
$this->assertInstanceOf(user_preference::class, $metadata[3]);
}
/**

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021101900.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021101900.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev+ (Build: 20211019)'; // Human-friendly version name