MDL-80258 reportbuilder: ensure consistent reporting of entity themes.

Courses, categories, users & cohorts each have a configurable `theme`
attribute - ensure reporting on said value is consistent across all the
corresponding entities.
This commit is contained in:
Paul Holden 2023-11-27 11:49:51 +00:00
parent a36c27c709
commit ebcc967d2e
No known key found for this signature in database
GPG Key ID: A81A96D6045F6164
9 changed files with 178 additions and 41 deletions

View File

@ -22,6 +22,7 @@ use context;
use context_helper;
use lang_string;
use stdClass;
use theme_config;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
@ -245,7 +246,14 @@ class cohort extends base {
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.theme")
->set_is_sortable(true);
->set_is_sortable(true)
->add_callback(static function (?string $theme): string {
if ((string) $theme === '') {
return '';
}
return get_string('pluginname', "theme_{$theme}");
});
return $columns;
}
@ -328,6 +336,22 @@ class cohort extends base {
))
->add_joins($this->get_joins());
// Theme filter.
$filters[] = (new filter(
select::class,
'theme',
new lang_string('theme'),
$this->get_entity_name(),
"{$tablealias}.theme",
))
->set_options_callback(static function(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
})
->add_joins($this->get_joins());
// Visible filter.
$filters[] = (new filter(
boolean_select::class,

View File

@ -92,6 +92,8 @@ class cohorts_test extends core_reportbuilder_testcase {
$this->resetAfterTest();
$this->setAdminUser();
set_config('allowcohortthemes', true);
/** @var core_customfield_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
@ -103,6 +105,7 @@ class cohorts_test extends core_reportbuilder_testcase {
'name' => 'Legends',
'idnumber' => 'C101',
'description' => 'Cohort for the legends',
'theme' => 'boost',
'customfield_hi' => 'Hello',
]);
@ -131,7 +134,7 @@ class cohorts_test extends core_reportbuilder_testcase {
$this->assertNotEmpty($timecreated);
$this->assertNotEmpty($timemodified);
$this->assertEquals('Created manually', $component);
$this->assertEmpty($theme);
$this->assertEquals('Boost', $theme);
$this->assertEquals('Hello', $custom);
$this->assertNotEmpty($timeadded);
$this->assertEquals(fullname($user), $fullname);
@ -184,6 +187,14 @@ class cohorts_test extends core_reportbuilder_testcase {
'Filter description (no match)' => ['cohort:description', [
'cohort:description_operator' => text::IS_EMPTY,
], false],
'Filter theme' => ['cohort:theme', [
'cohort:theme_operator' => select::EQUAL_TO,
'cohort:theme_value' => 'boost',
], true],
'Filter theme (no match)' => ['cohort:theme', [
'cohort:theme_operator' => select::EQUAL_TO,
'cohort:theme_value' => 'classic',
], false],
'Filter visible' => ['cohort:visible', [
'cohort:visible_operator' => boolean_select::CHECKED,
], true],
@ -225,11 +236,14 @@ class cohorts_test extends core_reportbuilder_testcase {
public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
$this->resetAfterTest();
set_config('allowcohortthemes', true);
// Test subject.
$cohort = $this->getDataGenerator()->create_cohort([
'name' => 'Legends',
'idnumber' => 'C101',
'description' => 'Cohort for the legends',
'theme' => 'boost',
]);
$user = $this->getDataGenerator()->create_user(['username' => 'lionel']);

View File

@ -24,9 +24,10 @@ use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use theme_config;
use core_course_category;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\{category, text};
use core_reportbuilder\local\filters\{category, select, text};
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
@ -199,6 +200,24 @@ class course_category extends base {
return format_text($description, $category->descriptionformat, ['context' => $context->id]);
});
// 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)
->add_callback(static function (?string $theme): string {
if ((string) $theme === '') {
return '';
}
return get_string('pluginname', "theme_{$theme}");
});
// Course count column.
$columns[] = (new column(
'coursecount',
@ -254,6 +273,22 @@ class course_category extends base {
))
->add_joins($this->get_joins());
// Theme filter.
$filters[] = (new filter(
select::class,
'theme',
new lang_string('theme'),
$this->get_entity_name(),
"{$tablealias}.theme",
))
->set_options_callback(static function(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
})
->add_joins($this->get_joins());
return $filters;
}

View File

@ -69,7 +69,14 @@ class categories_test extends core_reportbuilder_testcase {
$this->resetAfterTest();
$category = $this->getDataGenerator()->create_category(['name' => 'Zoo', 'idnumber' => 'Z01', 'description' => 'Animals']);
set_config('allowcategorythemes', true);
$category = $this->getDataGenerator()->create_category([
'name' => 'Zoo',
'idnumber' => 'Z01',
'description' => 'Animals',
'theme' => 'boost',
]);
$course = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'Zebra']);
// Add a cohort.
@ -88,6 +95,7 @@ class categories_test extends core_reportbuilder_testcase {
'sortenabled' => 1]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:path']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:description']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course_category:theme']);
// Add column from each of our entities.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'course:fullname']);
@ -98,19 +106,23 @@ class categories_test extends core_reportbuilder_testcase {
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(2, $content);
[$namewithlink, $path, $description, $coursename, $cohortname, $rolename, $userfullname] = array_values($content[0]);
[$namewithlink, $path, $description, $theme, $coursename, $cohortname, $rolename, $userfullname] =
array_values($content[0]);
$this->assertStringContainsString(get_string('defaultcategoryname'), $namewithlink);
$this->assertEquals(get_string('defaultcategoryname'), $path);
$this->assertEmpty($description);
$this->assertEmpty($theme);
$this->assertEmpty($coursename);
$this->assertEmpty($cohortname);
$this->assertEmpty($rolename);
$this->assertEmpty($userfullname);
[$namewithlink, $path, $description, $coursename, $cohortname, $rolename, $userfullname] = array_values($content[1]);
[$namewithlink, $path, $description, $theme, $coursename, $cohortname, $rolename, $userfullname] =
array_values($content[1]);
$this->assertStringContainsString($category->get_formatted_name(), $namewithlink);
$this->assertEquals($category->get_nested_name(false), $path);
$this->assertEquals(format_text($category->description, $category->descriptionformat), $description);
$this->assertEquals('Boost', $theme);
$this->assertEquals($course->fullname, $coursename);
$this->assertEquals($cohort->name, $cohortname);
$this->assertEquals('Manager', $rolename);
@ -151,6 +163,14 @@ class categories_test extends core_reportbuilder_testcase {
'course_category:idnumber_operator' => text::IS_EQUAL_TO,
'course_category:idnumber_value' => 'P01',
], false],
'Filter category theme' => ['course_category:theme', [
'course_category:theme_operator' => select::EQUAL_TO,
'course_category:theme_value' => 'boost',
], true],
'Filter category theme (no match)' => ['course_category:theme', [
'course_category:theme_operator' => select::EQUAL_TO,
'course_category:theme_value' => 'classic',
], false],
// Course.
'Filter course fullname' => ['course:fullname', [
@ -208,8 +228,10 @@ class categories_test extends core_reportbuilder_testcase {
$this->resetAfterTest();
set_config('allowcategorythemes', true);
// Get the default category, modify it so we can filter each value.
($category = core_course_category::get_default())->update(['name' => 'Zoo', 'idnumber' => 'Z01']);
($category = core_course_category::get_default())->update(['name' => 'Zoo', 'idnumber' => 'Z01', 'theme' => 'boost']);
$course = $this->getDataGenerator()->create_course(['category' => $category->id, 'fullname' => 'Zebra']);
// Add a cohort.

View File

@ -91,6 +91,7 @@ class courses_test extends core_reportbuilder_testcase {
'category' => $category->id,
'fullname' => 'Cats',
'summary' => 'Course description',
'theme' => 'boost',
'tags' => ['Horses'],
]);
@ -171,7 +172,7 @@ class courses_test extends core_reportbuilder_testcase {
$this->assertEquals('No', $coursegroupmodeforce);
$this->assertEmpty($courselang);
$this->assertEmpty($coursecalendar);
$this->assertEmpty($coursetheme);
$this->assertEquals('Boost', $coursetheme);
$this->assertEquals('No', $coursecompletion);
$this->assertEmpty($coursedownload);
$this->assertEquals(userdate($course->timecreated), $coursetimecreated);

View File

@ -32,6 +32,7 @@ use core_reportbuilder\local\report\filter;
use html_writer;
use lang_string;
use stdClass;
use theme_config;
defined('MOODLE_INTERNAL') || die();
@ -140,7 +141,7 @@ class course extends base {
'groupmodeforce' => new lang_string('groupmodeforce', 'group'),
'lang' => new lang_string('forcelanguage'),
'calendartype' => new lang_string('forcecalendartype', 'calendar'),
'theme' => new lang_string('forcetheme'),
'theme' => new lang_string('theme'),
'enablecompletion' => new lang_string('enablecompletion', 'completion'),
'downloadcontent' => new lang_string('downloadcoursecontent', 'course'),
'timecreated' => new lang_string('timecreated', 'core_reportbuilder'),
@ -411,16 +412,10 @@ class course extends base {
* @return array
*/
public static function get_options_for_theme(): array {
$options = [];
$themeobjects = get_list_of_themes();
foreach ($themeobjects as $key => $theme) {
if (empty($theme->hidefromselector)) {
$options[$key] = get_string('pluginname', "theme_{$theme->name}");
}
}
return $options;
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
}
/**
@ -454,15 +449,16 @@ class course extends base {
return format::userdate($value, $row);
}
if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) {
return format::boolean_as_text($value);
}
// If the column has corresponding filter, determine the value from its options.
$options = $this->get_options_for($fieldname);
if ($options !== null && array_key_exists($value, $options)) {
return $options[$value];
}
if ($this->get_course_field_type($fieldname) === column::TYPE_BOOLEAN) {
return format::boolean_as_text($value);
}
if (in_array($fieldname, ['fullname', 'shortname'])) {
if (!$row->courseid) {
return '';

View File

@ -27,6 +27,7 @@ use html_writer;
use lang_string;
use moodle_url;
use stdClass;
use theme_config;
use core_user\fields;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\date;
@ -311,14 +312,8 @@ class user extends base {
->set_is_sortable($this->is_sortable($userfield))
->add_callback([$this, 'format'], $userfield);
// Some columns also have specific format callbacks.
if ($userfield === 'country') {
$column->add_callback(static function(string $country): string {
$countries = get_string_manager()->get_list_of_countries(true);
return $countries[$country] ?? '';
});
} else if ($userfield === 'description') {
// Select enough fields in order to format the column.
// Join on the context table so that we can use it for formatting these columns later.
if ($userfield === 'description') {
$column
->add_join("LEFT JOIN {context} {$contexttablealias}
ON {$contexttablealias}.contextlevel = " . CONTEXT_USER . "
@ -368,6 +363,12 @@ class user extends base {
return format::userdate($value, $row);
}
// If the column has corresponding filter, determine the value from its options.
$options = $this->get_options_for($fieldname);
if ($options !== null && array_key_exists($value, $options)) {
return $options[$value];
}
if ($fieldname === 'description') {
if (empty($row->id)) {
return '';
@ -428,6 +429,7 @@ class user extends base {
'email' => new lang_string('email'),
'city' => new lang_string('city'),
'country' => new lang_string('country'),
'theme' => new lang_string('theme'),
'description' => new lang_string('description'),
'firstnamephonetic' => new lang_string('firstnamephonetic'),
'lastnamephonetic' => new lang_string('lastnamephonetic'),
@ -577,12 +579,43 @@ class user extends base {
return $filters;
}
/**
* Gets list of options if the filter supports it
*
* @param string $fieldname
* @return null|array
*/
protected function get_options_for(string $fieldname): ?array {
static $cached = [];
if (!array_key_exists($fieldname, $cached)) {
$callable = [static::class, 'get_options_for_' . $fieldname];
if (is_callable($callable)) {
$cached[$fieldname] = $callable();
} else {
$cached[$fieldname] = null;
}
}
return $cached[$fieldname];
}
/**
* List of options for the field country.
*
* @return string[]
*/
public static function get_options_for_country(): array {
return array_map('shorten_text', get_string_manager()->get_list_of_countries());
return get_string_manager()->get_list_of_countries();
}
/**
* List of options for the field theme.
*
* @return array
*/
public static function get_options_for_theme(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
}
}

View File

@ -55,7 +55,7 @@ class datasource_test extends advanced_testcase {
'All column' => [
[],
[],
29,
30,
],
'Include columns (picture, fullname, fullnamewithlink, fullnamewithpicture, fullnamewithpicturelink)' => [
['picture', 'fullname*'],
@ -65,7 +65,7 @@ class datasource_test extends advanced_testcase {
'Exclude columns (picture, fullname, fullnamewithlink, fullnamewithpicture, fullnamewithpicturelink)' => [
[],
['picture', 'fullname*'],
24,
25,
],
];
}
@ -112,7 +112,7 @@ class datasource_test extends advanced_testcase {
'All filters' => [
[],
[],
27,
28,
],
'Include filters (department, phone1, phone2)' => [
['department', 'phone*'],
@ -122,7 +122,7 @@ class datasource_test extends advanced_testcase {
'Exclude filters (department, phone1, phone2)' => [
[],
['department', 'phone*'],
24,
25,
],
];
}
@ -169,7 +169,7 @@ class datasource_test extends advanced_testcase {
'All conditions' => [
[],
[],
27,
28,
],
'Include conditions (department, phone1, phone2)' => [
['department', 'phone*'],
@ -179,7 +179,7 @@ class datasource_test extends advanced_testcase {
'Exclude conditions (department, phone1, phone2)' => [
[],
['department', 'phone*'],
24,
25,
],
];
}

View File

@ -77,6 +77,7 @@ class users_test extends core_reportbuilder_testcase {
'idnumber' => 'U0001',
'city' => 'London',
'country' => 'GB',
'theme' => 'boost',
'interests' => ['Horses'],
]);
@ -114,6 +115,7 @@ class users_test extends core_reportbuilder_testcase {
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:moodlenetprofile']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:timecreated']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:lastip']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:theme']);
// Tags.
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'tag:name']);
@ -159,9 +161,10 @@ class users_test extends core_reportbuilder_testcase {
$this->assertEquals($user->moodlenetprofile, $userrow[22]);
$this->assertNotEmpty($userrow[23]);
$this->assertEquals('0.0.0.0', $userrow[24]);
$this->assertEquals('Horses', $userrow[25]);
$this->assertStringContainsString('Horses', $userrow[26]);
$this->assertEquals($cohort->name, $userrow[27]);
$this->assertEquals('Boost', $userrow[25]);
$this->assertEquals('Horses', $userrow[26]);
$this->assertStringContainsString('Horses', $userrow[27]);
$this->assertEquals($cohort->name, $userrow[28]);
}
/**
@ -280,6 +283,14 @@ class users_test extends core_reportbuilder_testcase {
'user:country_operator' => select::EQUAL_TO,
'user:country_value' => 'AU',
], false],
'Filter theme' => ['user:theme', [
'user:theme_operator' => select::EQUAL_TO,
'user:theme_value' => 'boost',
], true],
'Filter theme (no match)' => ['user:theme', [
'user:theme_operator' => select::EQUAL_TO,
'user:theme_value' => 'classic',
], false],
'Filter description' => ['user:description', [
'user:description_operator' => text::CONTAINS,
'user:description_value' => 'Hello there',
@ -424,6 +435,7 @@ class users_test extends core_reportbuilder_testcase {
'address' => 'Big Farm',
'city' => 'Barcelona',
'country' => 'ES',
'theme' => 'boost',
'description' => 'Hello there',
'moodlenetprofile' => '@zoe1@example.com',
'interests' => ['Horses'],