diff --git a/lang/en/hub.php b/lang/en/hub.php index 55bd0164432..ad83ffd6096 100644 --- a/lang/en/hub.php +++ b/lang/en/hub.php @@ -24,6 +24,7 @@ */ $string['activeparticipantnumberaverage'] = 'Average number of recently active participants ({$a})'; $string['activeusersnumber'] = 'Number of recently active users ({$a})'; +$string['aiusagedata'] = 'AI usage data'; $string['aiusagestats'] = 'AI usage stats ({$a->timefrom} - {$a->timeto})'; $string['analyticsactions'] = 'Number of actions taken on generated predictions ({$a})'; $string['analyticsactionsnotuseful'] = 'Number of actions marking a prediction as not useful ({$a})'; @@ -34,6 +35,7 @@ $string['audience_help'] = 'Select the intended audience for the course.'; $string['audienceeducators'] = 'Educators'; $string['audiencestudents'] = 'Students'; $string['audienceadmins'] = 'Moodle administrators'; +$string['average_time'] = 'Average processing time ({$a})'; $string['badgesnumber'] = 'Number of badges ({$a})'; $string['communityremoved'] = 'That course link has been removed from your list'; $string['confirmregistration'] = 'Confirm registration'; @@ -64,6 +66,7 @@ $string['errorotherhubsnotsupported'] = 'This page can no longer be used for reg $string['errorws'] = '{$a}'; $string['errorwstokenreset'] = '{$a}. Registration token on this site has been reset. You can now register your site again.'; $string['errorregistrationupdate'] = 'An error occurred during registration update ({$a})'; +$string['fail_count'] = 'Fail count ({$a})'; $string['geolocation'] = 'Geolocation'; $string['geolocation_help'] = 'In future we may provide location-based searching. If you want to specify the location for your course use a latitude/longitude value here (eg: -31.947884,115.871285). One way to find this is to use Google Maps.'; $string['imageurl'] = 'Image URL'; @@ -78,6 +81,7 @@ $string['licence_link'] = 'licenses'; $string['logourl'] = 'Logo URL'; $string['mobilenotificationsenabled'] = 'Mobile notifications enabled ({$a})'; $string['mobileservicesenabled'] = 'Mobile services enabled ({$a})'; +$string['models'] = 'Models'; $string['modulenumberaverage'] = 'Average number of course modules ({$a})'; $string['mustselectsubject'] = 'You must select a subject'; $string['name'] = 'Name'; @@ -85,6 +89,7 @@ $string['name_help'] = 'This name will be showing in the course listing.'; $string['neverchecked'] = 'Never checked'; $string['next'] = 'Next'; $string['no'] = 'No'; +$string['noaiusagedata'] = 'AI usage data (none)'; $string['nocheckstatusfromunreghub'] = 'The site is not registered on the hub so the status can not be checked.'; $string['nohubselected'] = 'No hub selected'; $string['none'] = 'None'; @@ -96,6 +101,7 @@ $string['policyagreeddesc'] = 'I agree to the Pri $string['postaladdress'] = 'Postal address'; $string['postaladdress_help'] = 'Postal address of this site, or of the entity represented by this site.'; $string['postsnumber'] = 'Number of posts ({$a})'; +$string['predominant_error'] = 'Predominant HTTP error ({$a})'; $string['previousregistrationdeleted'] = 'The previous registration has been deleted from {$a}. You can restart the registration process. Thank you.'; $string['primaryauthtype'] = 'Primary authentication type ({$a})'; $string['questionsnumber'] = 'Number of questions ({$a})'; @@ -185,8 +191,12 @@ $string['siteurl_help'] = 'The URL is the address of this site. If privacy sett $string['siteversion'] = 'Moodle version'; $string['siteversion_help'] = 'The Moodle version of this site.'; $string['skipregistration'] = 'Skip'; +$string['success_count'] = 'Success count ({$a})'; $string['subject'] = 'Subject'; $string['subject_help'] = 'Select the main subject area which the course covers.'; +$string['timefrom'] = 'Time from ({$a})'; +$string['timeto'] = 'Time to ({$a})'; +$string['time_range'] = 'Time range'; $string['type'] = 'Shared'; $string['unregister'] = 'Unregister'; $string['unregistrationerror'] = 'An error occurred while attempting to unregister the site: {$a}'; diff --git a/lib/classes/hub/registration.php b/lib/classes/hub/registration.php index 88dd42eb20b..114c52890ee 100644 --- a/lib/classes/hub/registration.php +++ b/lib/classes/hub/registration.php @@ -239,6 +239,8 @@ class registration { * @return string */ public static function get_stats_summary($siteinfo) { + global $OUTPUT; + $fieldsneedconfirm = self::get_new_registration_fields(); $summary = html_writer::tag('p', get_string('sendfollowinginfo_help', 'hub')) . html_writer::start_tag('ul'); @@ -284,7 +286,7 @@ class registration { 'sitetheme' => get_string('sitetheme', 'hub', $siteinfo['sitetheme']), 'primaryauthtype' => get_string('primaryauthtype', 'hub', $siteinfo['primaryauthtype']), 'pluginusage' => get_string('pluginusagedata', 'hub', $pluginusagelinks), - 'aiusage' => get_string('aiusagestats', 'hub', self::get_ai_usage_time_range(true)), + 'aiusage' => $OUTPUT->render_from_template('core/ai_usage_data', ['aiusagedata' => self::show_ai_usage()]), ]; foreach ($senddata as $key => $str) { @@ -725,6 +727,75 @@ class registration { ]; } + /** + * Displays AI usage data for all providers. + * + * @return array Array containing usage data, grouped by provider + */ + public static function show_ai_usage(): array { + // Initialize aiusage collection. + $aiusage = []; + + // Process each provider's data. + foreach (self::get_ai_usage_data() as $provider => $actions) { + if ($provider === 'time_range') { + $aiusage['timerange'] = [ + 'label' => get_string($provider, 'hub'), + 'values' => self::format_ai_usage_actions($actions), + ]; + } else { + // Initialize provider data structure. + $aiusage['providers'][] = [ + 'providername' => get_string('pluginname', $provider), + 'aiactions' => self::format_ai_usage_actions($actions), + ]; + } + } + + return $aiusage; + } + + /** + * Formats individual actions for a provider. + * + * @param array $actions Raw actions data + * @return array Formatted action data + */ + private static function format_ai_usage_actions(array $actions): array { + $formattedactions = []; + + foreach ($actions as $action => $values) { + if (in_array($action, ['timefrom', 'timeto'])) { + $formattedactions[] = get_string($action, 'hub', userdate($values)); + } else { + $formattedactions[] = [ + 'actionname' => get_string("action_$action", 'core_ai'), + 'aiactionvalues' => self::format_ai_usage_action_values($values), + ]; + } + } + + return $formattedactions; + } + + /** + * Formats action values into formatted strings. + * + * @param array $values Action values to format + * @return array Formatted action values + */ + private static function format_ai_usage_action_values(array $values): array { + $formattedvalues = []; + + foreach ($values as $key => $value) { + if (get_string_manager()->string_exists($key, 'hub', $value)) { + $formattedvalues[]['values'] = get_string($key, 'hub', $value); + } + } + + return $formattedvalues; + } + /** * Get AI usage data. * diff --git a/lib/templates/ai_usage_data.mustache b/lib/templates/ai_usage_data.mustache new file mode 100644 index 00000000000..b86d50239e2 --- /dev/null +++ b/lib/templates/ai_usage_data.mustache @@ -0,0 +1,93 @@ +{{! + 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 . +}} +{{! + @template core/ai_usage_data + + Template for AI usage data. + + Example context (json): + { + "aiusagedata": [{ + "providers": [{ + "providerName": "AI provider name", + "aiactions": [{ + "actionName": "AI action name", + "aiactionvalues": [{ + "success_count": "1", + "fail_count": "2", + "average_time": "15", + "predominant_error": "403", + "models": [{ + "gpt-4o": "1" + }] + }] + }] + }], + "timerange": [{ + "label": "Time range for stats", + "values": [{ + "timefrom": "Time from: Friday, 2 May 2025, 1:22 AM", + "timeto": "Time from: Friday, 3 May 2025, 1:22 AM" + }] + }] + }] + } +}} + +{{#aiusagedata}} + {{#str}} aiusagedata, hub {{/str}}: + +{{/aiusagedata}} +{{^aiusagedata}} + {{#str}} noaiusagedata, hub {{/str}} +{{/aiusagedata}} diff --git a/lib/tests/hub/registration_test.php b/lib/tests/hub/registration_test.php index 033bc9b9fa4..457a3cd48b2 100644 --- a/lib/tests/hub/registration_test.php +++ b/lib/tests/hub/registration_test.php @@ -90,13 +90,39 @@ final class registration_test extends \advanced_testcase { * Test the AI usage data is calculated correctly. */ public function test_get_ai_usage(): void { - global $CFG, $DB; $this->resetAfterTest(); - $clock = $this->mock_clock_with_frozen(); + + // Let's generate some data first. + $clock = $this->mock_clock_with_frozen(1700000000); + $this->generate_ai_usage_data(); + + // Get our site info and check the expected calculations are correct. + $siteinfo = registration::get_site_info(); + $aisuage = json_decode($siteinfo['aiusage']); + // Check generated text. + $this->assertEquals(1, $aisuage->aiprovider_openai->generate_text->success_count); + $this->assertEquals(0, $aisuage->aiprovider_openai->generate_text->fail_count); + // Check generated images. + $this->assertEquals(2, $aisuage->aiprovider_openai->generate_image->success_count); + $this->assertEquals(3, $aisuage->aiprovider_openai->generate_image->fail_count); + $this->assertEquals(15, $aisuage->aiprovider_openai->generate_image->average_time); + $this->assertEquals(403, $aisuage->aiprovider_openai->generate_image->predominant_error); + // Check time range is set correctly. + $this->assertEquals($clock->time() - WEEKSECS, $aisuage->time_range->timefrom); + $this->assertEquals($clock->time(), $aisuage->time_range->timeto); + } + + /** + * Create some AI usage data. + */ + private function generate_ai_usage_data(): void { + global $DB; + + $clock = $this->mock_clock_with_frozen(1700000000); // Record some generated text. $record = new \stdClass(); - $record->provider = 'openai'; + $record->provider = 'aiprovider_openai'; $record->actionname = 'generate_text'; $record->actionid = 1; $record->userid = 1; @@ -108,11 +134,11 @@ final class registration_test extends \advanced_testcase { // Record a generated image. $record->actionname = 'generate_image'; - $record->actionid = 2; + $record->actionid = 111; $record->timecreated = $clock->time() - 20; $DB->insert_record('ai_action_register', $record); // Record another image. - $record->actionid = 3; + $record->actionid = 222; $record->timecreated = $clock->time() - 10; $DB->insert_record('ai_action_register', $record); @@ -128,20 +154,38 @@ final class registration_test extends \advanced_testcase { $record->actionid = 6; $record->errorcode = 404; $DB->insert_record('ai_action_register', $record); + } - // Get our site info and check the expected calculations are correct. - $siteinfo = registration::get_site_info(); - $aisuage = json_decode($siteinfo['aiusage']); - // Check generated text. - $this->assertEquals(1, $aisuage->openai->generate_text->success_count); - $this->assertEquals(0, $aisuage->openai->generate_text->fail_count); - // Check generated images. - $this->assertEquals(2, $aisuage->openai->generate_image->success_count); - $this->assertEquals(3, $aisuage->openai->generate_image->fail_count); - $this->assertEquals(15, $aisuage->openai->generate_image->average_time); - $this->assertEquals(403, $aisuage->openai->generate_image->predominant_error); - // Check time range is set correctly. - $this->assertEquals($clock->time() - WEEKSECS, $aisuage->time_range->timefrom); - $this->assertEquals($clock->time(), $aisuage->time_range->timeto); + /** + * Test the show AI usage data. + */ + public function test_show_ai_usage(): void { + $this->resetAfterTest(); + + // Init the registration class. + $registration = new registration(); + + // There should be no data to show yet. + $aisuagedata = $registration->show_ai_usage(); + $this->assertTrue(empty($aisuagedata)); + + // After generating some data, there should now be some data to show. + $this->generate_ai_usage_data(); + $aisuagedata = $registration->show_ai_usage(); + $this->assertTrue(!empty($aisuagedata)); + + foreach ($aisuagedata['providers'] as $provider) { + $this->assertEquals('OpenAI API provider', $provider['providername']); + $this->assertTrue(!empty($provider['aiactions'])); + + foreach ($provider['aiactions'] as $action) { + $actionname = $action['actionname']; + $this->assertTrue(!empty($actionname)); + } + } + + $timerange = $aisuagedata['timerange']; + $this->assertEquals(get_string('time_range', 'hub'), $timerange['label']); + $this->assertTrue(!empty($timerange['values'])); } }