MDL-59630 analytics: Purge everything when the model is deleted
authorDavid Monllao <davidm@moodle.com>
Wed, 30 Aug 2017 10:53:38 +0000 (12:53 +0200)
committerDavid Monllao <davidm@moodle.com>
Tue, 3 Oct 2017 08:07:32 +0000 (10:07 +0200)
admin/tool/analytics/classes/output/form/edit_model.php
analytics/classes/model.php
analytics/classes/prediction.php
analytics/tests/model_test.php

index c8f7129..66270b2 100644 (file)
@@ -45,7 +45,7 @@ class edit_model extends \moodleform {
 
         $mform = $this->_form;
 
-        if ($this->_customdata['model']->get_model_obj()->trained == 1) {
+        if ($this->_customdata['model']->is_trained()) {
             $message = get_string('edittrainedwarning', 'tool_analytics');
             $mform->addElement('html', $OUTPUT->notification($message, \core\output\notification::NOTIFY_WARNING));
         }
index fc7f43d..d8b1ab2 100644 (file)
@@ -441,9 +441,6 @@ class model {
             // Delete generated predictions.
             $this->clear_model();
 
-            // Purge all generated files.
-            \core_analytics\dataset_manager::clear_model_files($this->model->id);
-
             // Reset trained flag.
             $this->model->trained = 0;
 
@@ -476,6 +473,7 @@ class model {
 
         $this->clear_model();
         $DB->delete_records('analytics_models', array('id' => $this->model->id));
+        $DB->delete_records('analytics_models_log', array('modelid' => $this->model->id));
     }
 
     /**
@@ -1413,11 +1411,21 @@ class model {
     private function clear_model() {
         global $DB;
 
+        $predictionids = $DB->get_fieldset_select('analytics_predictions', 'id', 'modelid = :modelid',
+            array('modelid' => $this->get_id()));
+        if ($predictionids) {
+            list($sql, $params) = $DB->get_in_or_equal($predictionids);
+            $DB->delete_records_select('analytics_prediction_actions', "predictionid $sql", $params);
+        }
+
         $DB->delete_records('analytics_predictions', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_predict_samples', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_train_samples', array('modelid' => $this->model->id));
         $DB->delete_records('analytics_used_files', array('modelid' => $this->model->id));
 
+        // Purge all generated files.
+        \core_analytics\dataset_manager::clear_model_files($this->model->id);
+
         // We don't expect people to clear models regularly and the cost of filling the cache is
         // 1 db read per context.
         $this->purge_insights_cache();
index 549795e..24fa77a 100644 (file)
@@ -68,7 +68,7 @@ class prediction {
     /**
      * Constructor
      *
-     * @param \stdClass $prediction
+     * @param \stdClass|int $prediction
      * @param array $sampledata
      * @return void
      */
index b8e1768..48bd630 100644 (file)
@@ -77,6 +77,93 @@ class analytics_model_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core_analytics\model', $model);
     }
 
+    /**
+     * test_delete
+     */
+    public function test_delete() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        set_config('enabled_stores', 'logstore_standard', 'tool_log');
+
+        $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
+        $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
+        $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
+        $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
+
+        $this->model->enable('\core\analytics\time_splitting\no_splitting');
+
+        $this->model->train();
+        $this->model->predict();
+
+        // Fake evaluation results record to check that it is actually deleted.
+        $this->add_fake_log();
+
+        // Generate a prediction action to confirm that it is deleted when there is an important update.
+        $predictions = $DB->get_records('analytics_predictions');
+        $prediction = reset($predictions);
+        $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
+        $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
+
+        $this->model->delete();
+        $this->assertEmpty($DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
+        $this->assertEmpty($DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
+        $this->assertEmpty($DB->count_records('analytics_predictions'));
+        $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
+        $this->assertEmpty($DB->count_records('analytics_train_samples'));
+        $this->assertEmpty($DB->count_records('analytics_predict_samples'));
+        $this->assertEmpty($DB->count_records('analytics_used_files'));
+
+        set_config('enabled_stores', '', 'tool_log');
+        get_log_manager(true);
+    }
+
+    /**
+     * test_clear
+     */
+    public function test_clear() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        set_config('enabled_stores', 'logstore_standard', 'tool_log');
+
+        $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
+        $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
+        $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
+        $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
+
+        $this->model->enable('\core\analytics\time_splitting\no_splitting');
+
+        $this->model->train();
+        $this->model->predict();
+
+        // Fake evaluation results record to check that it is actually deleted.
+        $this->add_fake_log();
+
+        // Generate a prediction action to confirm that it is deleted when there is an important update.
+        $predictions = $DB->get_records('analytics_predictions');
+        $prediction = reset($predictions);
+        $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
+        $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
+
+        // Update to an empty time splitting method to force clear_model execution.
+        $this->model->update(1, false, '');
+        // Restore previous time splitting method.
+        $this->model->enable('\core\analytics\time_splitting\no_splitting');
+
+        // Check that most of the stuff got deleted.
+        $this->assertEquals(1, $DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
+        $this->assertEquals(1, $DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
+        $this->assertEmpty($DB->count_records('analytics_predictions'));
+        $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
+        $this->assertEmpty($DB->count_records('analytics_train_samples'));
+        $this->assertEmpty($DB->count_records('analytics_predict_samples'));
+        $this->assertEmpty($DB->count_records('analytics_used_files'));
+
+        set_config('enabled_stores', '', 'tool_log');
+        get_log_manager(true);
+    }
+
     public function test_model_manager() {
         $this->resetAfterTest(true);
 
@@ -159,6 +246,25 @@ class analytics_model_testcase extends advanced_testcase {
         $target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
         $this->assertTrue(\core_analytics\model::exists($target));
     }
+
+    /**
+     * Generates a model log record.
+     */
+    private function add_fake_log() {
+        global $DB, $USER;
+
+        $log = new stdClass();
+        $log->modelid = $this->modelobj->id;
+        $log->version = $this->modelobj->version;
+        $log->target = $this->modelobj->target;
+        $log->indicators = $this->modelobj->indicators;
+        $log->score = 1;
+        $log->info = json_encode([]);
+        $log->dir = 'not important';
+        $log->timecreated = time();
+        $log->usermodified = $USER->id;
+        $DB->insert_record('analytics_models_log', $log);
+    }
 }
 
 /**