From a2f4f5bd3bca7b0a52d98de7e60f4bd92a00d42a Mon Sep 17 00:00:00 2001 From: David Woloszyn Date: Thu, 7 Nov 2024 15:58:52 +1100 Subject: [PATCH] MDL-83396 core_ai: Create AI policy acceptance report --- admin/settings/ai.php | 11 ++ .../local/entities/ai_policy_register.php | 116 ++++++++++++++++++ .../local/systemreports/policy_acceptance.php | 100 +++++++++++++++ ai/policy_acceptance_report.php | 45 +++++++ ai/tests/behat/reports.feature | 60 +++++++++ lang/en/ai.php | 4 + lang/en/role.php | 1 + lib/db/access.php | 9 ++ version.php | 2 +- 9 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 ai/classes/reportbuilder/local/entities/ai_policy_register.php create mode 100644 ai/classes/reportbuilder/local/systemreports/policy_acceptance.php create mode 100644 ai/policy_acceptance_report.php create mode 100644 ai/tests/behat/reports.feature diff --git a/admin/settings/ai.php b/admin/settings/ai.php index 9f41f6e3ab6..838ac7abcef 100644 --- a/admin/settings/ai.php +++ b/admin/settings/ai.php @@ -67,3 +67,14 @@ if ($hassiteconfig) { $plugin->load_settings($ADMIN, 'ai', $hassiteconfig); } } + +// AI reports category. +$ADMIN->add('reports', new admin_category('aireports', get_string('aireports', 'core_ai'))); +// Add AI policy acceptance report. +$aipolicyacceptance = new admin_externalpage( + 'aipolicyacceptancereport', + get_string('aipolicyacceptance', 'core_ai'), + new moodle_url('/ai/policy_acceptance_report.php'), + 'moodle/ai:viewaipolicyacceptancereport' +); +$ADMIN->add('aireports', $aipolicyacceptance); diff --git a/ai/classes/reportbuilder/local/entities/ai_policy_register.php b/ai/classes/reportbuilder/local/entities/ai_policy_register.php new file mode 100644 index 00000000000..a71abb00a44 --- /dev/null +++ b/ai/classes/reportbuilder/local/entities/ai_policy_register.php @@ -0,0 +1,116 @@ +. + +namespace core_ai\reportbuilder\local\entities; + +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; +use lang_string; + +/** + * AI policy register entity. + * + * Defines all the columns and filters that can be added to reports that use this entity. + * + * @package core_ai + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class ai_policy_register extends base { + + #[\Override] + protected function get_default_tables(): array { + return [ + 'ai_policy_register', + ]; + } + + #[\Override] + protected function get_default_entity_title(): lang_string { + return new lang_string('aipolicyregister', 'core_ai'); + } + + #[\Override] + 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('ai_policy_register'); + + // Time accepted column. + $columns[] = (new column( + 'timeaccepted', + new lang_string('dateaccepted', 'core_ai'), + $this->get_entity_name(), + )) + ->add_joins($this->get_joins()) + ->set_type(column::TYPE_TIMESTAMP) + ->add_field("{$tablealias}.timeaccepted") + ->set_is_sortable(true) + ->add_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('ai_policy_register'); + + // Time accepted filter. + $filters[] = (new filter( + date::class, + 'timeaccepted', + new lang_string('dateaccepted', 'core_ai'), + $this->get_entity_name(), + "{$tablealias}.timeaccepted", + )) + ->add_joins($this->get_joins()) + ->set_limited_operators([ + date::DATE_ANY, + date::DATE_RANGE, + date::DATE_PREVIOUS, + date::DATE_CURRENT, + ]); + + return $filters; + } +} diff --git a/ai/classes/reportbuilder/local/systemreports/policy_acceptance.php b/ai/classes/reportbuilder/local/systemreports/policy_acceptance.php new file mode 100644 index 00000000000..82ef974c566 --- /dev/null +++ b/ai/classes/reportbuilder/local/systemreports/policy_acceptance.php @@ -0,0 +1,100 @@ +. + +namespace core_ai\reportbuilder\local\systemreports; + +use core_ai\reportbuilder\local\entities\ai_policy_register; +use core_reportbuilder\system_report; +use core_reportbuilder\local\entities\user; + +/** + * AI policy acceptance system report. + * + * @package core_ai + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class policy_acceptance extends system_report { + + #[\Override] + protected function initialise(): void { + $entitymain = new ai_policy_register(); + $entitymainalias = $entitymain->get_table_alias('ai_policy_register'); + + $this->set_main_table('ai_policy_register', $entitymainalias); + $this->add_entity($entitymain); + + // Join the 'user' entity to our main entity. + $entityuser = new user(); + $entituseralias = $entityuser->get_table_alias('user'); + $this->add_entity($entityuser->add_join( + "LEFT JOIN {user} {$entituseralias} ON {$entituseralias}.id = {$entitymainalias}.userid" + )); + + // Any columns required by actions should be defined here to ensure they're always available. + $this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.userid"); + + // Now we can call our helper methods to add the content we want to include in the report. + $this->add_columns(); + $this->add_filters(); + + // Set if report can be downloaded. + $this->set_downloadable(true, get_string('aipolicyacceptance', 'core_ai')); + } + + #[\Override] + protected function can_view(): bool { + return has_capability('moodle/ai:viewaipolicyacceptancereport', $this->get_context()); + } + + #[\Override] + public static function get_name(): string { + return get_string('aipolicyacceptance', 'core_ai'); + } + + /** + * Adds the columns we want to display in the report. + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier. + */ + public function add_columns(): void { + $columns = [ + 'user:fullnamewithlink', + 'ai_policy_register:timeaccepted', + ]; + + $this->add_columns_from_entities($columns); + + // It's possible to set a default initial sort direction for one column. + $this->set_initial_sort_column('ai_policy_register:timeaccepted', SORT_DESC); + } + + /** + * Adds the filters we want to display in the report. + * + * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their + * unique identifier. + */ + protected function add_filters(): void { + $filters = [ + 'user:fullname', + 'ai_policy_register:timeaccepted', + ]; + + $this->add_filters_from_entities($filters); + } +} diff --git a/ai/policy_acceptance_report.php b/ai/policy_acceptance_report.php new file mode 100644 index 00000000000..40c613efea0 --- /dev/null +++ b/ai/policy_acceptance_report.php @@ -0,0 +1,45 @@ +. + +/** + * Display AI policy acceptance report. + * + * @package core_ai + * @copyright 2024 David Woloszyn + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_reportbuilder\system_report_factory; +use core_ai\reportbuilder\local\systemreports\policy_acceptance; + +require(__DIR__ . '/../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +admin_externalpage_setup('aipolicyacceptancereport'); + +// Set up the page. +$systemcontext = context_system::instance(); +$pageurl = new moodle_url($CFG->wwwroot . '/ai/policy_acceptance_report.php'); +$PAGE->set_url($pageurl); +$PAGE->set_context($systemcontext); +$PAGE->set_pagelayout('report'); +$PAGE->set_primary_active_tab('siteadminnode'); +echo $OUTPUT->header(); + +$report = system_report_factory::create(policy_acceptance::class, $systemcontext); +echo $OUTPUT->heading($report::get_name()); +echo $report->output(); +echo $OUTPUT->footer(); diff --git a/ai/tests/behat/reports.feature b/ai/tests/behat/reports.feature new file mode 100644 index 00000000000..4e6668e0a2c --- /dev/null +++ b/ai/tests/behat/reports.feature @@ -0,0 +1,60 @@ +@report @core_ai +Feature: AI reports + In order to view an AI report + As an admin or system role manager + I need to populate relevant data and navigate to the report page + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | manager1 | Manager | One | manager1@example.com | + And the following "role assigns" exist: + | user | role | contextlevel | reference | + | manager1 | manager | System | | + And the following config values are set as admin: + | enabled | 1 | aiprovider_openai | + | apikey | 123 | aiprovider_openai | + And the following config values are set as admin: + | enabled | 1 | aiplacement_editor | + | generate_text | 1 | aiplacement_editor | + | generate_image | 0 | aiplacement_editor | + + @javascript @editor_tiny + Scenario: Mangers with a system role can see who has accepted the AI policy + Given I am logged in as "admin" + # Accept the AI policy as admin. + And I open my profile in edit mode + And I select the "p" element in position "0" of the "Description" TinyMCE editor + And I expand all toolbars for the "Description" TinyMCE editor + And I click on the "AI generate text" button for the "Description" TinyMCE editor + And I click on "Accept and continue" "button" in the "AI usage policy" "dialogue" + And I press the escape key + # Accept the AI policy as manager1. + And I am logged in as "manager1" + And I open my profile in edit mode + And I select the "p" element in position "0" of the "Description" TinyMCE editor + And I expand all toolbars for the "Description" TinyMCE editor + And I click on the "AI generate text" button for the "Description" TinyMCE editor + And I click on "Accept and continue" "button" in the "AI usage policy" "dialogue" + And I press the escape key + # View the report. + When I navigate to "Reports > AI reports > AI policy acceptance" in site administration + Then I should see "Admin User" in the "reportbuilder-table" "table" + And I should see "Manager One" in the "reportbuilder-table" "table" + # Test date filter (check last 1 day). + And I click on "Filters" "button" + And I set the following fields in the "Date accepted" "core_reportbuilder > Filter" to these values: + | Date accepted operator | Last | + And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" + And I click on "Filters" "button" + And I should see "Admin User" in the "reportbuilder-table" "table" + And I should see "Manager One" in the "reportbuilder-table" "table" + # Test name filter. + And I click on "Filters" "button" + And I set the following fields in the "Full name" "core_reportbuilder > Filter" to these values: + | Full name operator | Is equal to | + | Full name value | Admin User | + And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element" + And I click on "Filters" "button" + And I should see "Admin User" in the "reportbuilder-table" "table" + And I should not see "Manager One" in the "reportbuilder-table" "table" diff --git a/lang/en/ai.php b/lang/en/ai.php index 89578422b40..3aa853c6db3 100644 --- a/lang/en/ai.php +++ b/lang/en/ai.php @@ -49,7 +49,10 @@ $string['actionsettingprovider'] = '{$a} action settings'; $string['actionsettingprovider_desc'] = 'These settings control how the {$a->providername} performs the action {$a->actionname}.'; $string['ai'] = 'AI'; $string['aiplacements'] = 'AI placements'; +$string['aipolicyacceptance'] = 'AI policy acceptance'; +$string['aipolicyregister'] = 'AI policy register'; $string['aiproviders'] = 'AI providers'; +$string['aireports'] = 'AI reports'; $string['aiusagepolicy'] = 'AI usage policy'; $string['availableplacements'] = 'Choose where AI actions are available'; $string['availableplacements_desc'] = 'Placements define how and where AI actions can be used in your site. You can choose which actions are available in each placement through the settings.'; @@ -57,6 +60,7 @@ $string['availableproviders'] = 'Manage the AI providers connected to your LMS'; $string['availableproviders_desc'] = 'AI providers add AI functionality to your site through \'actions\' like text summarisation or image generation.
You can manage the actions for each provider in their settings.'; $string['contentwatermark'] = 'Generated by AI'; +$string['dateaccepted'] = 'Date accepted'; $string['declineaipolicy'] = 'Decline'; $string['manageaiplacements'] = 'Manage AI placements'; $string['manageaiproviders'] = 'Manage AI providers'; diff --git a/lang/en/role.php b/lang/en/role.php index eba80327124..efcab621808 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -25,6 +25,7 @@ $string['ai:acceptpolicy'] = 'Set a users AI policy acceptance'; $string['ai:fetchanyuserpolicystatus'] = 'Get a users AI policy acceptance'; $string['ai:fetchpolicy'] = 'Get a users AI policy acceptance'; +$string['ai:viewaipolicyacceptancereport'] = 'View AI policy acceptance report'; $string['addinganewrole'] = 'Adding a new role'; $string['addrole'] = 'Add a new role'; $string['advancedoverride'] = 'Advanced role override'; diff --git a/lib/db/access.php b/lib/db/access.php index 29db4c7b3e5..8bb36c16e65 100644 --- a/lib/db/access.php +++ b/lib/db/access.php @@ -2810,4 +2810,13 @@ $capabilities = array( 'user' => CAP_ALLOW, ], ], + // Allow managers to view the AI policy acceptance report. + 'moodle/ai:viewaipolicyacceptancereport' => [ + 'riskbitmask' => RISK_PERSONAL, + 'captype' => 'read', + 'contextlevel' => CONTEXT_SYSTEM, + 'archetypes' => [ + 'manager' => CAP_ALLOW, + ], + ], ); diff --git a/version.php b/version.php index 4d357e2b233..d4029c16c4e 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024112200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024112200.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '5.0dev (Build: 20241122)'; // Human-friendly version name