diff --git a/admin/tool/analytics/classes/output/form/edit_model.php b/admin/tool/analytics/classes/output/form/edit_model.php index c8f71299409..66270b25cbe 100644 --- a/admin/tool/analytics/classes/output/form/edit_model.php +++ b/admin/tool/analytics/classes/output/form/edit_model.php @@ -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)); } diff --git a/analytics/classes/model.php b/analytics/classes/model.php index fc7f43d1af0..d8b1ab2216f 100644 --- a/analytics/classes/model.php +++ b/analytics/classes/model.php @@ -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(); diff --git a/analytics/classes/prediction.php b/analytics/classes/prediction.php index 549795e816b..24fa77a12e7 100644 --- a/analytics/classes/prediction.php +++ b/analytics/classes/prediction.php @@ -68,7 +68,7 @@ class prediction { /** * Constructor * - * @param \stdClass $prediction + * @param \stdClass|int $prediction * @param array $sampledata * @return void */ diff --git a/analytics/tests/model_test.php b/analytics/tests/model_test.php index b8e1768ded8..48bd6300762 100644 --- a/analytics/tests/model_test.php +++ b/analytics/tests/model_test.php @@ -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); + } } /**