MDL-61667 analytics: Create missing models on install/upgrade
authorDavid Mudrák <david@moodle.com>
Tue, 12 Mar 2019 08:45:26 +0000 (09:45 +0100)
committerDavid Mudrák <david@moodle.com>
Mon, 1 Apr 2019 12:23:06 +0000 (14:23 +0200)
Similarly to how the scheduled tasks work, we now automatically check
and make sure that all the models specified in the component's
db/analytics.php file exist during the installation or upgrade of the
component.

analytics/classes/manager.php
analytics/tests/manager_test.php
analytics/upgrade.txt
lib/db/analytics.php [new file with mode: 0644]
lib/db/upgrade.php
lib/upgradelib.php

index 9ee20f5..2994616 100644 (file)
@@ -607,6 +607,27 @@ class manager {
         return $classes;
     }
 
+    /**
+     * Check that all the models declared by the component are up to date.
+     *
+     * This is intended to be called during the installation / upgrade to automatically create missing models.
+     *
+     * @param string $componentname The name of the component to load models for.
+     * @return array \core_analytics\model[] List of actually created models.
+     */
+    public static function update_default_models_for_component(string $componentname): array {
+
+        $result = [];
+
+        foreach (static::load_default_models_for_component($componentname) as $definition) {
+            if (!\core_analytics\model::exists(static::get_target($definition['target']))) {
+                $result[] = static::create_declared_model($definition);
+            }
+        }
+
+        return $result;
+    }
+
     /**
      * Return the list of models declared by the given component.
      *
index 7034461..e685bed 100644 (file)
@@ -337,4 +337,38 @@ class analytics_manager_testcase extends advanced_testcase {
         $existing->update(0, false, false);
         $this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
     }
+
+    /**
+     * Test the implementation of the {@link \core_analytics\manager::update_default_models_for_component()}.
+     */
+    public function test_update_default_models_for_component() {
+
+        $this->resetAfterTest();
+        $this->setAdminuser();
+
+        $noteaching = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
+        $dropout = \core_analytics\manager::get_target('\core\analytics\target\course_dropout');
+
+        $this->assertTrue(\core_analytics\model::exists($noteaching));
+        $this->assertTrue(\core_analytics\model::exists($dropout));
+
+        foreach (\core_analytics\manager::get_all_models() as $model) {
+            $model->delete();
+        }
+
+        $this->assertFalse(\core_analytics\model::exists($noteaching));
+        $this->assertFalse(\core_analytics\model::exists($dropout));
+
+        $updated = \core_analytics\manager::update_default_models_for_component('moodle');
+
+        $this->assertEquals(2, count($updated));
+        $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
+        $this->assertTrue(array_pop($updated) instanceof \core_analytics\model);
+        $this->assertTrue(\core_analytics\model::exists($noteaching));
+        $this->assertTrue(\core_analytics\model::exists($dropout));
+
+        $repeated = \core_analytics\manager::update_default_models_for_component('moodle');
+
+        $this->assertSame([], $repeated);
+    }
 }
index 8d1932e..d2036f2 100644 (file)
@@ -6,6 +6,9 @@ information provided here is intended especially for developers.
 * \core_analytics\regressor::evaluate_regression and \core_analytics\classifier::evaluate_classification
   have been updated to include a new $trainedmodeldir param. This new param will be used to evaluate the
   existing trained model.
+* Plugins and core subsystems can now declare default prediction models by describing them in
+  their db/analytics.php file. Models should not be created manually via the db/install.php
+  file any more.
 * The method \core_analytics\manager::add_builtin_models() has been deprecated. The functionality
   has been replaced with automatic update of models provided by the core moodle component. There
   is no need to call this method explicitly any more. Instead, adding new models can be achieved
diff --git a/lib/db/analytics.php b/lib/db/analytics.php
new file mode 100644 (file)
index 0000000..821ad95
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// This file is part of Moodle - https://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/>.
+
+/**
+ * Defines the built-in prediction models provided by the Moodle core.
+ *
+ * @package     core
+ * @category    analytics
+ * @copyright   2019 David Mudrák <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$models = [
+    [
+        'target' => '\core\analytics\target\course_dropout',
+        'indicators' => [
+            '\core\analytics\indicator\any_access_after_end',
+            '\core\analytics\indicator\any_access_before_start',
+            '\core\analytics\indicator\any_write_action_in_course',
+            '\core\analytics\indicator\read_actions',
+            '\core_course\analytics\indicator\completion_enabled',
+            '\core_course\analytics\indicator\potential_cognitive_depth',
+            '\core_course\analytics\indicator\potential_social_breadth',
+            '\mod_assign\analytics\indicator\cognitive_depth',
+            '\mod_assign\analytics\indicator\social_breadth',
+            '\mod_book\analytics\indicator\cognitive_depth',
+            '\mod_book\analytics\indicator\social_breadth',
+            '\mod_chat\analytics\indicator\cognitive_depth',
+            '\mod_chat\analytics\indicator\social_breadth',
+            '\mod_choice\analytics\indicator\cognitive_depth',
+            '\mod_choice\analytics\indicator\social_breadth',
+            '\mod_data\analytics\indicator\cognitive_depth',
+            '\mod_data\analytics\indicator\social_breadth',
+            '\mod_feedback\analytics\indicator\cognitive_depth',
+            '\mod_feedback\analytics\indicator\social_breadth',
+            '\mod_folder\analytics\indicator\cognitive_depth',
+            '\mod_folder\analytics\indicator\social_breadth',
+            '\mod_forum\analytics\indicator\cognitive_depth',
+            '\mod_forum\analytics\indicator\social_breadth',
+            '\mod_glossary\analytics\indicator\cognitive_depth',
+            '\mod_glossary\analytics\indicator\social_breadth',
+            '\mod_imscp\analytics\indicator\cognitive_depth',
+            '\mod_imscp\analytics\indicator\social_breadth',
+            '\mod_label\analytics\indicator\cognitive_depth',
+            '\mod_label\analytics\indicator\social_breadth',
+            '\mod_lesson\analytics\indicator\cognitive_depth',
+            '\mod_lesson\analytics\indicator\social_breadth',
+            '\mod_lti\analytics\indicator\cognitive_depth',
+            '\mod_lti\analytics\indicator\social_breadth',
+            '\mod_page\analytics\indicator\cognitive_depth',
+            '\mod_page\analytics\indicator\social_breadth',
+            '\mod_quiz\analytics\indicator\cognitive_depth',
+            '\mod_quiz\analytics\indicator\social_breadth',
+            '\mod_resource\analytics\indicator\cognitive_depth',
+            '\mod_resource\analytics\indicator\social_breadth',
+            '\mod_scorm\analytics\indicator\cognitive_depth',
+            '\mod_scorm\analytics\indicator\social_breadth',
+            '\mod_survey\analytics\indicator\cognitive_depth',
+            '\mod_survey\analytics\indicator\social_breadth',
+            '\mod_url\analytics\indicator\cognitive_depth',
+            '\mod_url\analytics\indicator\social_breadth',
+            '\mod_wiki\analytics\indicator\cognitive_depth',
+            '\mod_wiki\analytics\indicator\social_breadth',
+            '\mod_workshop\analytics\indicator\cognitive_depth',
+            '\mod_workshop\analytics\indicator\social_breadth',
+        ],
+    ],
+    [
+        'target' => '\core\analytics\target\no_teaching',
+        'indicators' => [
+            '\core_course\analytics\indicator\no_teacher',
+            '\core_course\analytics\indicator\no_student',
+        ],
+        'timesplitting' => '\core\analytics\time_splitting\single_range',
+        'enabled' => true,
+    ],
+];
index 7d28a63..c3fe166 100644 (file)
@@ -774,80 +774,6 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->create_table($table);
         }
 
-        $now = time();
-        $admin = get_admin();
-
-        $targetname = '\core\analytics\target\course_dropout';
-        if (!$DB->record_exists('analytics_models', array('target' => $targetname))) {
-            // We can not use API calls to create the built-in models.
-            $modelobj = new stdClass();
-            $modelobj->target = $targetname;
-            $modelobj->indicators = json_encode(array(
-                '\mod_assign\analytics\indicator\cognitive_depth',
-                '\mod_assign\analytics\indicator\social_breadth',
-                '\mod_book\analytics\indicator\cognitive_depth',
-                '\mod_book\analytics\indicator\social_breadth',
-                '\mod_chat\analytics\indicator\cognitive_depth',
-                '\mod_chat\analytics\indicator\social_breadth',
-                '\mod_choice\analytics\indicator\cognitive_depth',
-                '\mod_choice\analytics\indicator\social_breadth',
-                '\mod_data\analytics\indicator\cognitive_depth',
-                '\mod_data\analytics\indicator\social_breadth',
-                '\mod_feedback\analytics\indicator\cognitive_depth',
-                '\mod_feedback\analytics\indicator\social_breadth',
-                '\mod_folder\analytics\indicator\cognitive_depth',
-                '\mod_folder\analytics\indicator\social_breadth',
-                '\mod_forum\analytics\indicator\cognitive_depth',
-                '\mod_forum\analytics\indicator\social_breadth',
-                '\mod_glossary\analytics\indicator\cognitive_depth',
-                '\mod_glossary\analytics\indicator\social_breadth',
-                '\mod_imscp\analytics\indicator\cognitive_depth',
-                '\mod_imscp\analytics\indicator\social_breadth',
-                '\mod_label\analytics\indicator\cognitive_depth',
-                '\mod_label\analytics\indicator\social_breadth',
-                '\mod_lesson\analytics\indicator\cognitive_depth',
-                '\mod_lesson\analytics\indicator\social_breadth',
-                '\mod_lti\analytics\indicator\cognitive_depth',
-                '\mod_lti\analytics\indicator\social_breadth',
-                '\mod_page\analytics\indicator\cognitive_depth',
-                '\mod_page\analytics\indicator\social_breadth',
-                '\mod_quiz\analytics\indicator\cognitive_depth',
-                '\mod_quiz\analytics\indicator\social_breadth',
-                '\mod_resource\analytics\indicator\cognitive_depth',
-                '\mod_resource\analytics\indicator\social_breadth',
-                '\mod_scorm\analytics\indicator\cognitive_depth',
-                '\mod_scorm\analytics\indicator\social_breadth',
-                '\mod_survey\analytics\indicator\cognitive_depth',
-                '\mod_survey\analytics\indicator\social_breadth',
-                '\mod_url\analytics\indicator\cognitive_depth',
-                '\mod_url\analytics\indicator\social_breadth',
-                '\mod_wiki\analytics\indicator\cognitive_depth',
-                '\mod_wiki\analytics\indicator\social_breadth',
-                '\mod_workshop\analytics\indicator\cognitive_depth',
-                '\mod_workshop\analytics\indicator\social_breadth',
-            ));
-            $modelobj->version = $now;
-            $modelobj->timecreated = $now;
-            $modelobj->timemodified = $now;
-            $modelobj->usermodified = $admin->id;
-            $DB->insert_record('analytics_models', $modelobj);
-        }
-
-        $targetname = '\core\analytics\target\no_teaching';
-        if (!$DB->record_exists('analytics_models', array('target' => $targetname))) {
-            $modelobj = new stdClass();
-            $modelobj->enabled = 1;
-            $modelobj->trained = 1;
-            $modelobj->target = $targetname;
-            $modelobj->indicators = json_encode(array('\core_course\analytics\indicator\no_teacher'));
-            $modelobj->timesplitting = '\core\analytics\time_splitting\single_range';
-            $modelobj->version = $now;
-            $modelobj->timecreated = $now;
-            $modelobj->timemodified = $now;
-            $modelobj->usermodified = $admin->id;
-            $DB->insert_record('analytics_models', $modelobj);
-        }
-
         // Main savepoint reached.
         upgrade_main_savepoint(true, 2017072000.02);
     }
index b02579f..318d570 100644 (file)
@@ -578,6 +578,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
                     log_update_descriptions($component);
                     external_update_descriptions($component);
                     \core\task\manager::reset_scheduled_tasks_for_component($component);
+                    \core_analytics\manager::update_default_models_for_component($component);
                     message_update_providers($component);
                     \core\message\inbound\manager::update_handlers_for_component($component);
                     if ($type === 'message') {
@@ -616,6 +617,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             if ($type === 'message') {
@@ -649,6 +651,7 @@ function upgrade_plugins($type, $startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             if ($type === 'message') {
@@ -756,6 +759,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
                     log_update_descriptions($component);
                     external_update_descriptions($component);
                     \core\task\manager::reset_scheduled_tasks_for_component($component);
+                    \core_analytics\manager::update_default_models_for_component($component);
                     message_update_providers($component);
                     \core\message\inbound\manager::update_handlers_for_component($component);
                     upgrade_plugin_mnet_functions($component);
@@ -790,6 +794,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             upgrade_plugin_mnet_functions($component);
@@ -826,6 +831,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             upgrade_plugin_mnet_functions($component);
@@ -947,6 +953,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
                     log_update_descriptions($component);
                     external_update_descriptions($component);
                     \core\task\manager::reset_scheduled_tasks_for_component($component);
+                    \core_analytics\manager::update_default_models_for_component($component);
                     message_update_providers($component);
                     \core\message\inbound\manager::update_handlers_for_component($component);
                     upgrade_plugin_mnet_functions($component);
@@ -987,6 +994,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             core_tag_area::reset_definitions_for_component($component);
@@ -1022,6 +1030,7 @@ function upgrade_plugins_blocks($startcallback, $endcallback, $verbose) {
             log_update_descriptions($component);
             external_update_descriptions($component);
             \core\task\manager::reset_scheduled_tasks_for_component($component);
+            \core_analytics\manager::update_default_models_for_component($component);
             message_update_providers($component);
             \core\message\inbound\manager::update_handlers_for_component($component);
             upgrade_plugin_mnet_functions($component);
@@ -1738,6 +1747,7 @@ function install_core($version, $verbose) {
         log_update_descriptions('moodle');
         external_update_descriptions('moodle');
         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
+        \core_analytics\manager::update_default_models_for_component('moodle');
         message_update_providers('moodle');
         \core\message\inbound\manager::update_handlers_for_component('moodle');
         core_tag_area::reset_definitions_for_component('moodle');
@@ -1805,6 +1815,7 @@ function upgrade_core($version, $verbose) {
         log_update_descriptions('moodle');
         external_update_descriptions('moodle');
         \core\task\manager::reset_scheduled_tasks_for_component('moodle');
+        \core_analytics\manager::update_default_models_for_component('moodle');
         message_update_providers('moodle');
         \core\message\inbound\manager::update_handlers_for_component('moodle');
         core_tag_area::reset_definitions_for_component('moodle');