Commit bbf95413 authored by Paul Holden's avatar Paul Holden Committed by David Matamoros
Browse files

MDL-72172 cohort: implement cohort datasource for custom reporting.



Create two entities exposing reportable data on site cohorts and
their members, via column and filter definitions.

Create report source bringing them together along with the user
entity to provide data for the reportbuilder editor.

Co-authored-by: Carlos Castillo's avatarCarlos Castillo <carlos.castillo@moodle.com>
parent 2b2897bf
<?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_cohort\local\entities;
use context;
use context_helper;
use lang_string;
use stdClass;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Cohort entity
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort extends base {
/**
* Database tables that this entity uses and their default aliases
*
* @return array
*/
protected function get_default_table_aliases(): array {
return ['cohort' => 'c'];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('cohort', 'core_cohort');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('cohort');
// Category/context column.
$columns[] = (new column(
'context',
new lang_string('category'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_INTEGER)
->add_fields("{$tablealias}.contextid")
->set_is_sortable(true)
->add_callback(static function(int $contextid): string {
return context::instance_by_id($contextid)->get_context_name(false);
});
// Name column.
$columns[] = (new column(
'name',
new lang_string('name', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name")
->set_is_sortable(true);
// ID number column.
$columns[] = (new column(
'idnumber',
new lang_string('idnumber', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.idnumber")
->set_is_sortable(true);
// Description column.
$columns[] = (new column(
'description',
new lang_string('description'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.description, {$tablealias}.descriptionformat, {$tablealias}.id, {$tablealias}.contextid")
->add_callback(static function(string $description, stdClass $cohort): string {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
$description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $cohort->contextid, 'cohort',
'description', $cohort->id);
return format_text($description, $cohort->descriptionformat, ['context' => $cohort->contextid]);
})
->set_is_sortable(false);
// Visible column.
$columns[] = (new column(
'visible',
new lang_string('visible', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_fields("{$tablealias}.visible")
->set_is_sortable(true)
->set_callback([format::class, 'boolean_as_text']);
// Time created column.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timecreated")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Time modified column.
$columns[] = (new column(
'timemodified',
new lang_string('timemodified', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timemodified")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Component column.
$columns[] = (new column(
'component',
new lang_string('component', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.component")
->set_is_sortable(true)
->add_callback(static function(string $component): string {
return empty($component)
? get_string('nocomponent', 'cohort')
: get_string('pluginname', $component);
});
// Theme column.
$columns[] = (new column(
'theme',
new lang_string('theme'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.theme")
->set_is_sortable(true);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('cohort');
// Context filter.
$filters[] = (new filter(
select::class,
'context',
new lang_string('category'),
$this->get_entity_name(),
"{$tablealias}.contextid"
))
->add_joins($this->get_joins())
->set_options_callback(static function(): array {
global $DB;
// Load all contexts in which there are cohorts.
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
$contexts = $DB->get_records_sql("
SELECT DISTINCT {$ctxfields}, c.contextid
FROM {context} ctx
JOIN {cohort} c ON c.contextid = ctx.id");
// Transform context record into it's name (used as the filter options).
return array_map(static function(stdClass $contextrecord): string {
context_helper::preload_from_record($contextrecord);
return context::instance_by_id($contextrecord->contextid)
->get_context_name(false);
}, $contexts);
});
// Name filter.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.name"
))
->add_joins($this->get_joins());
// ID number filter.
$filters[] = (new filter(
text::class,
'idnumber',
new lang_string('idnumber', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.idnumber"
))
->add_joins($this->get_joins());
// Time created filter.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.timecreated"
))
->add_joins($this->get_joins());
return $filters;
}
}
<?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_cohort\local\entities;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Cohort member entity
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_member extends base {
/**
* Database tables that this entity uses and their default aliases
*
* @return array
*/
protected function get_default_table_aliases(): array {
return ['cohort_members' => 'cm'];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('cohortmember', 'core_cohort');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('cohort_members');
// Time added column.
$columns[] = (new column(
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timeadded")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('cohort_members');
// Time added filter.
$filters[] = (new filter(
date::class,
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.timeadded"
))
->add_joins($this->get_joins());
return $filters;
}
}
<?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_cohort\reportbuilder\datasource;
use core_cohort\local\entities\cohort;
use core_cohort\local\entities\cohort_member;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\user;
/**
* Cohorts datasource
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohorts extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('cohorts', 'core_cohort');
}
/**
* Initialise report
*/
protected function initialise(): void {
$cohortentity = new cohort();
$cohorttablealias = $cohortentity->get_table_alias('cohort');
$this->set_main_table('cohort', $cohorttablealias);
$this->add_entity($cohortentity);
// Join the cohort member entity to the cohort entity.
$cohortmemberentity = new cohort_member();
$cohortmembertablealias = $cohortmemberentity->get_table_alias('cohort_members');
$cohortmemberjoin = "LEFT JOIN {cohort_members} {$cohortmembertablealias}
ON {$cohortmembertablealias}.cohortid = {$cohorttablealias}.id";
$this->add_entity($cohortmemberentity->add_join($cohortmemberjoin));
// Join the user entity to the cohort member entity.
$userentity = new user();
$usertablealias = $userentity->get_table_alias('user');
$userjoin = "LEFT JOIN {user} {$usertablealias}
ON {$usertablealias}.id = {$cohortmembertablealias}.userid";
$this->add_entity($userentity->add_joins([$cohortmemberjoin, $userjoin]));
// Add all columns from entities to be available in custom reports.
$this->add_columns_from_entity($cohortentity->get_entity_name());
$this->add_columns_from_entity($cohortmemberentity->get_entity_name());
$this->add_columns_from_entity($userentity->get_entity_name());
// Add all filters from entities to be available in custom reports.
$this->add_filters_from_entity($cohortentity->get_entity_name());
$this->add_filters_from_entity($cohortmemberentity->get_entity_name());
$this->add_filters_from_entity($userentity->get_entity_name());
// Add all conditions from entities to be available in custom reports.
$this->add_conditions_from_entity($cohortentity->get_entity_name());
$this->add_conditions_from_entity($cohortmemberentity->get_entity_name());
$this->add_conditions_from_entity($userentity->get_entity_name());
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'cohort:context',
'cohort:name',
'cohort:idnumber',
'cohort:description',
];
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
public function get_default_filters(): array {
return ['cohort:context', 'cohort:name'];
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
public function get_default_conditions(): array {
return [];
}
}
@core_reportbuilder @javascript
Feature: Manage custom reports for cohorts
In order to manage custom reports for cohorts
As an admin and user
I need to create new, view and edit existing reports
Background:
Given the following "cohorts" exist:
| name | idnumber | contextid |
| Another one | AO | 1 |
| MDL-62161 | 62161 | 1 |
| New system cohort | NSC | 1 |
| MDL-62162 | 62162 | 1 |
| Other cohort | LC | 3 |
And the following "users" exist:
| username | firstname | lastname | email |
| user1 | Alice | Last1 | user1@example.com |
| user2 | Carlos | Last2 | user2@example.com |
| user3 | Paul | Last3 | user3@example.com |
| user4 | Juan | Last4 | user4@example.com |
| user5 | Pedro | Last5 | user5@example.com |
| user6 | Luis | Last6 | user6@example.com |
| user7 | David | Last7 | user7@example.com |
| user8 | Zoe | Last8 | user8@example.com |
And the following "cohort members" exist:
| user | cohort |
| user1 | AO |
| user2 | AO |
| user3 | AO |
| user4 | AO |
| user5 | 62161 |
| user6 | 62161 |
| user7 | NSC |
| user8 | NSC |
And the following "core_reportbuilder > Reports" exist:
| name | source | default |
| My report | core_cohort\reportbuilder\datasource\cohorts | 0 |
And the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | cohort:context |
| My report | cohort:name |
Scenario: Add condition to cohorts report
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I click on "Show/hide settings sidebar" "button"
And I click on "Show/hide 'Conditions'" "button"
Then I should see "There are no conditions selected" in the "[data-region='settings-conditions']" "css_element"
And I set the field "Select a condition" to "Category"
And I should see "Added condition 'Category'"
And I should not see "There are no conditions selected" in the "[data-region='settings-conditions']" "css_element"
And I set the following fields in the "Category" "core_reportbuilder > Condition" to these values:
| Category operator | Is equal to |
| Category value | 3 |
And I click on "Apply" "button" in the "[data-region='settings-conditions']" "css_element"
And I should see "Conditions applied"
And I should see "Other cohort" in the "reportbuilder-table" "table"
And I should not see "MDL-62162" in the "reportbuilder-table" "table"
Scenario: Use filters in cohorts report
Given I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I change window size to "large"
When I click on "Show/hide settings sidebar" "button"
And I click on "Show/hide 'Filters'" "button"
Then I should see "There are no filters selected" in the "[data-region='settings-filters']" "css_element"
And I set the field "Select a filter" to "Name"
And I should see "Other cohort" in the ".reportbuilder-table" "css_element"
And I should see "MDL-62162" in the ".reportbuilder-table" "css_element"
When I click on "Switch to preview mode" "button"
And I click on "Filters" "button" in the "[data-region='core_reportbuilder/report-header']" "css_element"
And I set the following fields in the "Name" "core_reportbuilder > Filter" to these values:
| Name operator | Contains |
| Name value | Another |
And I click on "Apply" "button" in the "[data-region='core_reportbuilder/report-header']" "css_element"
Then the following should exist in the "reportbuilder-table" table:
| Category | Name |
| System | Another one |
And the following should not exist in the "reportbuilder-table" table:
| Category | Name |
| Miscellaneous | Other cohort |
Scenario: Use sorting and aggregations in cohorts report
Given the following "core_reportbuilder > Columns" exist:
| report | uniqueidentifier |
| My report | user:lastname |
And I am on the "My report" "reportbuilder > Editor" page logged in as "admin"
And I set the field "Rename column 'Surname'" to "Members"
And I reload the page