Merge branch 'MDL-61667-master-modelsinstall' of git://github.com/mudrd8mz/moodle

This commit is contained in:
Eloy Lafuente (stronk7)
2019-04-02 00:20:47 +02:00
18 changed files with 807 additions and 171 deletions
+164 -74
View File
@@ -40,6 +40,11 @@ class manager {
*/
const DEFAULT_MLBACKEND = '\mlbackend_php\processor';
/**
* Name of the file where components declare their models.
*/
const ANALYTICS_FILENAME = 'db/analytics.php';
/**
* @var \core_analytics\predictor[]
*/
@@ -107,7 +112,9 @@ class manager {
$params['trained'] = 1;
}
if ($predictioncontext) {
$conditions[] = "EXISTS (SELECT 'x' FROM {analytics_predictions} ap WHERE ap.modelid = am.id AND ap.contextid = :contextid)";
$conditions[] = "EXISTS (SELECT 'x'
FROM {analytics_predictions} ap
WHERE ap.modelid = am.id AND ap.contextid = :contextid)";
$params['contextid'] = $predictioncontext->id;
}
$sql .= ' WHERE ' . implode(' AND ', $conditions);
@@ -502,84 +509,16 @@ class manager {
}
/**
* Adds the models included with moodle core to the system.
* Used to be used to add models included with the Moodle core.
*
* @deprecated Deprecated since Moodle 3.7 (MDL-61667) - Use lib/db/analytics.php instead.
* @todo Remove this method in Moodle 4.1 (MDL-65186).
* @return void
*/
public static function add_builtin_models() {
$target = self::get_target('\core\analytics\target\course_dropout');
// Community of inquiry indicators.
$coiindicators = 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',
'\core_course\analytics\indicator\completion_enabled',
'\core_course\analytics\indicator\potential_cognitive_depth',
'\core_course\analytics\indicator\potential_social_breadth',
'\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',
);
$indicators = array();
foreach ($coiindicators as $coiindicator) {
$indicator = self::get_indicator($coiindicator);
$indicators[$indicator->get_id()] = $indicator;
}
if (!\core_analytics\model::exists($target, $indicators)) {
$model = \core_analytics\model::create($target, $indicators);
}
// No teaching model.
$target = self::get_target('\core\analytics\target\no_teaching');
$timesplittingmethod = '\core\analytics\time_splitting\single_range';
$noteacher = self::get_indicator('\core_course\analytics\indicator\no_teacher');
$nostudent = self::get_indicator('\core_course\analytics\indicator\no_student');
$indicators = array($noteacher->get_id() => $noteacher, $nostudent->get_id() => $nostudent);
if (!\core_analytics\model::exists($target, $indicators)) {
\core_analytics\model::create($target, $indicators, $timesplittingmethod);
}
debugging('core_analytics\manager::add_builtin_models() has been deprecated. Core models are now automatically '.
'updated according to their declaration in the lib/db/analytics.php file.', DEBUG_DEVELOPER);
}
/**
@@ -669,4 +608,155 @@ 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.
*
* @param string $componentname The name of the component to load models for.
* @throws \coding_exception Exception thrown in case of invalid syntax.
* @return array The $models description array.
*/
public static function load_default_models_for_component(string $componentname): array {
$dir = \core_component::get_component_directory($componentname);
if (!$dir) {
// This is either an invalid component, or a core subsystem without its own root directory.
return [];
}
$file = $dir . '/' . self::ANALYTICS_FILENAME;
if (!is_readable($file)) {
return [];
}
$models = null;
include($file);
if (!isset($models) || !is_array($models) || empty($models)) {
return [];
}
foreach ($models as &$model) {
if (!isset($model['enabled'])) {
$model['enabled'] = false;
} else {
$model['enabled'] = clean_param($model['enabled'], PARAM_BOOL);
}
}
static::validate_models_declaration($models);
return $models;
}
/**
* Validate the declaration of prediction models according the syntax expected in the component's db folder.
*
* The expected structure looks like this:
*
* [
* [
* 'target' => '\fully\qualified\name\of\the\target\class',
* 'indicators' => [
* '\fully\qualified\name\of\the\first\indicator',
* '\fully\qualified\name\of\the\second\indicator',
* ],
* 'timesplitting' => '\optional\name\of\the\time_splitting\class',
* 'enabled' => true,
* ],
* ];
*
* @param array $models List of declared models.
* @throws \coding_exception Exception thrown in case of invalid syntax.
*/
public static function validate_models_declaration(array $models) {
foreach ($models as $model) {
if (!isset($model['target'])) {
throw new \coding_exception('Missing target declaration');
}
if (!static::is_valid($model['target'], '\core_analytics\local\target\base')) {
throw new \coding_exception('Invalid target classname', $model['target']);
}
if (empty($model['indicators']) || !is_array($model['indicators'])) {
throw new \coding_exception('Missing indicators declaration');
}
foreach ($model['indicators'] as $indicator) {
if (!static::is_valid($indicator, '\core_analytics\local\indicator\base')) {
throw new \coding_exception('Invalid indicator classname', $indicator);
}
}
if (isset($model['timesplitting'])) {
if (substr($model['timesplitting'], 0, 1) !== '\\') {
throw new \coding_exception('Expecting fully qualified time splitting classname', $model['timesplitting']);
}
if (!static::is_valid($model['timesplitting'], '\core_analytics\local\time_splitting\base')) {
throw new \coding_exception('Invalid time splitting classname', $model['timesplitting']);
}
}
if (!empty($model['enabled']) && !isset($model['timesplitting'])) {
throw new \coding_exception('Cannot enable a model without time splitting method specified');
}
}
}
/**
* Create the defined model.
*
* @param array $definition See {@link self::validate_models_declaration()} for the syntax.
* @return \core_analytics\model
*/
public static function create_declared_model(array $definition): \core_analytics\model {
$target = static::get_target($definition['target']);
$indicators = [];
foreach ($definition['indicators'] as $indicatorname) {
$indicator = static::get_indicator($indicatorname);
$indicators[$indicator->get_id()] = $indicator;
}
if (isset($definition['timesplitting'])) {
$timesplitting = $definition['timesplitting'];
} else {
$timesplitting = false;
}
$created = \core_analytics\model::create($target, $indicators, $timesplitting);
if (!empty($definition['enabled'])) {
$created->enable();
}
return $created;
}
}
+18 -12
View File
@@ -346,8 +346,6 @@ class model {
$timesplittingid = false, $processor = null) {
global $USER, $DB;
\core_analytics\manager::check_can_manage_models();
$indicatorclasses = self::indicator_classes($indicators);
$now = time();
@@ -360,6 +358,20 @@ class model {
$modelobj->timemodified = $now;
$modelobj->usermodified = $USER->id;
if ($target->based_on_assumptions()) {
$modelobj->trained = 1;
}
if ($timesplittingid) {
if (!\core_analytics\manager::is_valid($timesplittingid, '\core_analytics\local\time_splitting\base')) {
throw new \moodle_exception('errorinvalidtimesplitting', 'analytics');
}
if (substr($timesplittingid, 0, 1) !== '\\') {
throw new \moodle_exception('errorinvalidtimesplitting', 'analytics');
}
$modelobj->timesplitting = $timesplittingid;
}
if ($processor &&
!manager::is_valid($processor, '\core_analytics\classifier') &&
!manager::is_valid($processor, '\core_analytics\regressor')) {
@@ -375,14 +387,6 @@ class model {
$model = new static($modelobj);
if ($timesplittingid) {
$model->enable($timesplittingid);
}
if ($model->is_static()) {
$model->mark_as_trained();
}
return $model;
}
@@ -401,6 +405,10 @@ class model {
$existingmodels = $DB->get_records('analytics_models', array('target' => $target->get_id()));
if (!$existingmodels) {
return false;
}
if (!$indicators && $existingmodels) {
return true;
}
@@ -1049,8 +1057,6 @@ class model {
public function enable($timesplittingid = false) {
global $DB, $USER;
\core_analytics\manager::check_can_manage_models();
$now = time();
if ($timesplittingid && $timesplittingid !== $this->model->timesplitting) {
@@ -0,0 +1,37 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'test_target_course_level_shortname',
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
],
// Cannot be enabled without timesplitting defined.
'enabled' => true,
],
];
@@ -0,0 +1,36 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'test_target_course_level_shortname',
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
'\non\existing\class\name',
],
],
];
@@ -0,0 +1,36 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'there_should_be_a_valid_fully_qualified_classname_here',
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
'\core_course\analytics\indicator\no_student',
],
],
];
@@ -0,0 +1,36 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'test_target_course_level_shortname',
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
],
'timesplitting' => '\non\existing\class\name',
],
];
@@ -0,0 +1,36 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'test_target_course_level_shortname',
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
],
'timesplitting' => 'local_customplugin_non_fully_qualified_class_name',
],
];
@@ -0,0 +1,32 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'target' => 'test_target_course_level_shortname',
],
];
@@ -0,0 +1,35 @@
<?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/>.
/**
* Example of an invalid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'indicators' => [
'\core_course\analytics\indicator\no_teacher',
'\core_course\analytics\indicator\no_student',
],
],
];
@@ -0,0 +1,38 @@
<?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/>.
/**
* Example of a valid db/analytics.php file content used for unit tests.
*
* @package core_analytics
* @category test
* @copyright 2019 David Mudrák <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$models = [
[
'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,
],
];
+221
View File
@@ -150,4 +150,225 @@ class analytics_manager_testcase extends advanced_testcase {
get_log_manager(true);
}
/**
* Tests for the {@link \core_analytics\manager::load_default_models_for_component()} implementation.
*/
public function test_load_default_models_for_component() {
$this->resetAfterTest();
// Attempting to load builtin models should always work without throwing exception.
\core_analytics\manager::load_default_models_for_component('core');
// Attempting to load from a core subsystem without its own subsystem directory.
$this->assertSame([], \core_analytics\manager::load_default_models_for_component('core_access'));
// Attempting to load from a non-existing subsystem.
$this->assertSame([], \core_analytics\manager::load_default_models_for_component('core_nonexistingsubsystem'));
// Attempting to load from a non-existing plugin of a known plugin type.
$this->assertSame([], \core_analytics\manager::load_default_models_for_component('mod_foobarbazquaz12240996776'));
// Attempting to load from a non-existing plugin type.
$this->assertSame([], \core_analytics\manager::load_default_models_for_component('foo_bar2776327736558'));
}
/**
* Tests for the successful execution of the {@link \core_analytics\manager::validate_models_declaration()}.
*/
public function test_validate_models_declaration() {
$this->resetAfterTest();
// This is expected to run without an exception.
$models = $this->load_models_from_fixture_file('no_teaching');
\core_analytics\manager::validate_models_declaration($models);
}
/**
* Tests for the exceptions thrown by {@link \core_analytics\manager::validate_models_declaration()}.
*
* @dataProvider validate_models_declaration_exceptions_provider
* @param array $models Models declaration.
* @param string $exception Expected coding exception message.
*/
public function test_validate_models_declaration_exceptions(array $models, string $exception) {
$this->resetAfterTest();
$this->expectException(\coding_exception::class);
$this->expectExceptionMessage($exception);
\core_analytics\manager::validate_models_declaration($models);
}
/**
* Data provider for the {@link self::test_validate_models_declaration_exceptions()}.
*
* @return array of (string)testcase => [(array)models, (string)expected exception message]
*/
public function validate_models_declaration_exceptions_provider() {
return [
'missing_target' => [
$this->load_models_from_fixture_file('missing_target'),
'Missing target declaration',
],
'invalid_target' => [
$this->load_models_from_fixture_file('invalid_target'),
'Invalid target classname',
],
'missing_indicators' => [
$this->load_models_from_fixture_file('missing_indicators'),
'Missing indicators declaration',
],
'invalid_indicators' => [
$this->load_models_from_fixture_file('invalid_indicators'),
'Invalid indicator classname',
],
'invalid_time_splitting' => [
$this->load_models_from_fixture_file('invalid_time_splitting'),
'Invalid time splitting classname',
],
'invalid_time_splitting_fq' => [
$this->load_models_from_fixture_file('invalid_time_splitting_fq'),
'Expecting fully qualified time splitting classname',
],
'invalid_enabled' => [
$this->load_models_from_fixture_file('invalid_enabled'),
'Cannot enable a model without time splitting method specified',
],
];
}
/**
* Loads models as declared in the given fixture file.
*
* @param string $filename
* @return array
*/
protected function load_models_from_fixture_file(string $filename) {
global $CFG;
$models = null;
require($CFG->dirroot.'/analytics/tests/fixtures/db_analytics_php/'.$filename.'.php');
return $models;
}
/**
* Test the implementation of the {@link \core_analytics\manager::create_declared_model()}.
*/
public function test_create_declared_model() {
global $DB;
$this->resetAfterTest();
$this->setAdminuser();
$declaration = [
'target' => 'test_target_course_level_shortname',
'indicators' => [
'test_indicator_max',
'test_indicator_min',
'test_indicator_fullname',
],
];
$declarationwithtimesplitting = array_merge($declaration, [
'timesplitting' => '\core\analytics\time_splitting\no_splitting',
]);
$declarationwithtimesplittingenabled = array_merge($declarationwithtimesplitting, [
'enabled' => true,
]);
// Check that no such model exists yet.
$target = \core_analytics\manager::get_target('test_target_course_level_shortname');
$this->assertEquals(0, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
$this->assertFalse(\core_analytics\model::exists($target));
// Check that the model is created.
$created = \core_analytics\manager::create_declared_model($declaration);
$this->assertTrue($created instanceof \core_analytics\model);
$this->assertTrue(\core_analytics\model::exists($target));
$this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
$modelid = $created->get_id();
// Check that created models are disabled by default.
$existing = new \core_analytics\model($modelid);
$this->assertEquals(0, $existing->get_model_obj()->enabled);
$this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
// Let the admin enable the model.
$existing->enable('\core\analytics\time_splitting\no_splitting');
$this->assertEquals(1, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
// Check that further calls create a new model.
$repeated = \core_analytics\manager::create_declared_model($declaration);
$this->assertTrue($repeated instanceof \core_analytics\model);
$this->assertEquals(2, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
// Delete the models.
$existing->delete();
$repeated->delete();
$this->assertEquals(0, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
$this->assertFalse(\core_analytics\model::exists($target));
// Create it again, this time with time splitting method specified.
$created = \core_analytics\manager::create_declared_model($declarationwithtimesplitting);
$this->assertTrue($created instanceof \core_analytics\model);
$this->assertTrue(\core_analytics\model::exists($target));
$this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
$modelid = $created->get_id();
// Even if the time splitting method was specified, the model is still not enabled automatically.
$existing = new \core_analytics\model($modelid);
$this->assertEquals(0, $existing->get_model_obj()->enabled);
$this->assertEquals(0, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
$existing->delete();
// Let's define the model so that it is enabled by default.
$enabled = \core_analytics\manager::create_declared_model($declarationwithtimesplittingenabled);
$this->assertTrue($enabled instanceof \core_analytics\model);
$this->assertTrue(\core_analytics\model::exists($target));
$this->assertEquals(1, $DB->count_records('analytics_models', ['target' => $target->get_id()]));
$modelid = $enabled->get_id();
$existing = new \core_analytics\model($modelid);
$this->assertEquals(1, $existing->get_model_obj()->enabled);
$this->assertEquals(1, $DB->get_field('analytics_models', 'enabled', ['target' => $target->get_id()], MUST_EXIST));
// Let the admin disable the model.
$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);
}
}
+6 -8
View File
@@ -272,16 +272,14 @@ class analytics_model_testcase extends advanced_testcase {
public function test_exists() {
$this->resetAfterTest(true);
global $DB;
$count = $DB->count_records('analytics_models');
// No new models added if the builtin ones already exist.
\core_analytics\manager::add_builtin_models();
$this->assertCount($count, $DB->get_records('analytics_models'));
$target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
$this->assertTrue(\core_analytics\model::exists($target));
foreach (\core_analytics\manager::get_all_models() as $model) {
$model->delete();
}
$this->assertFalse(\core_analytics\model::exists($target));
}
/**
+2
View File
@@ -86,8 +86,10 @@ class core_analytics_privacy_model_testcase extends \core_privacy\tests\provider
$this->setAdminUser();
$this->model1->enable();
$this->model1->train();
$this->model1->predict();
$this->model2->enable();
$this->model2->train();
$this->model2->predict();
+7
View File
@@ -6,6 +6,13 @@ 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
by updating the lib/db/analytics.php file and bumping the core version.
=== 3.5 ===
+92
View File
@@ -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 <[email protected]>
* @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,
],
];
-3
View File
@@ -319,7 +319,4 @@ function xmldb_main_install() {
require_once($CFG->libdir . '/db/upgradelib.php');
make_default_scale();
make_competence_scale();
// Add built-in prediction models.
\core_analytics\manager::add_builtin_models();
}
-74
View 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);
}
+11
View 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');