From 01d620ee67a29debc0a8b453a6093203b96d64b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Wed, 27 Mar 2019 15:31:58 +0100 Subject: [PATCH 1/4] MDL-64777 analytics: Add method for identifying a model declaration This is intended to be used to as a declaration identifier in HTML or as a HTTP parameter value. --- analytics/classes/manager.php | 10 ++++++++ analytics/tests/manager_test.php | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/analytics/classes/manager.php b/analytics/classes/manager.php index 4a4eede5dcd..1e31268e3dc 100644 --- a/analytics/classes/manager.php +++ b/analytics/classes/manager.php @@ -770,4 +770,14 @@ class manager { return $created; } + + /** + * Returns a string uniquely representing the given model declaration. + * + * @param array $model Model declaration + * @return string complying with PARAM_ALPHANUM rules and starting with an 'id' prefix + */ + public static function model_declaration_identifier(array $model) : string { + return 'id'.sha1(serialize($model)); + } } diff --git a/analytics/tests/manager_test.php b/analytics/tests/manager_test.php index 6d2cb693a13..923ad4060e1 100644 --- a/analytics/tests/manager_test.php +++ b/analytics/tests/manager_test.php @@ -403,4 +403,47 @@ class analytics_manager_testcase extends advanced_testcase { $defaultforevaluation = \core_analytics\manager::get_time_splitting_methods_for_evaluation(false); $this->assertArrayNotHasKey('\core\analytics\time_splitting\quarters', $defaultforevaluation); } + + /** + * Test the implementation of the {@link \core_analytics\manager::model_declaration_identifier()}. + */ + public function test_model_declaration_identifier() { + + $noteaching1 = $this->load_models_from_fixture_file('no_teaching'); + $noteaching2 = $this->load_models_from_fixture_file('no_teaching'); + $noteaching3 = $this->load_models_from_fixture_file('no_teaching'); + + // Same model declaration should always lead to same identifier. + $this->assertEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching1)), + \core_analytics\manager::model_declaration_identifier(reset($noteaching2)) + ); + + // If something is changed, the identifier should change, too. + $noteaching2[0]['target'] .= '_'; + $this->assertNotEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching1)), + \core_analytics\manager::model_declaration_identifier(reset($noteaching2)) + ); + + $noteaching3[0]['indicators'][] = '\core_analytics\local\indicator\binary'; + $this->assertNotEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching1)), + \core_analytics\manager::model_declaration_identifier(reset($noteaching3)) + ); + + // The identifier is supposed to contain PARAM_ALPHANUM only. + $this->assertEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching1)), + clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching1)), PARAM_ALPHANUM) + ); + $this->assertEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching2)), + clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching2)), PARAM_ALPHANUM) + ); + $this->assertEquals( + \core_analytics\manager::model_declaration_identifier(reset($noteaching3)), + clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching3)), PARAM_ALPHANUM) + ); + } } From 1297fa4156f53b7b9dad9c3f7818c5d0e224614e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Wed, 27 Mar 2019 10:39:20 +0100 Subject: [PATCH 2/4] MDL-64777 analytics: Add method to load all model definitions --- analytics/classes/manager.php | 37 ++++++++++++++++++++++++++++++++ analytics/tests/manager_test.php | 14 ++++++++++++ 2 files changed, 51 insertions(+) diff --git a/analytics/classes/manager.php b/analytics/classes/manager.php index 1e31268e3dc..c80925379eb 100644 --- a/analytics/classes/manager.php +++ b/analytics/classes/manager.php @@ -683,6 +683,43 @@ class manager { return $models; } + /** + * Return the list of all the models declared anywhere in this Moodle installation. + * + * Models defined by the core and core subsystems come first, followed by those provided by plugins. + * + * @return array indexed by the frankenstyle component + */ + public static function load_default_models_for_all_components(): array { + + $tmp = []; + + foreach (\core_component::get_component_list() as $type => $components) { + foreach (array_keys($components) as $component) { + if ($loaded = static::load_default_models_for_component($component)) { + $tmp[$type][$component] = $loaded; + } + } + } + + $result = []; + + if ($loaded = static::load_default_models_for_component('core')) { + $result['core'] = $loaded; + } + + if (!empty($tmp['core'])) { + $result += $tmp['core']; + unset($tmp['core']); + } + + foreach ($tmp as $components) { + $result += $components; + } + + return $result; + } + /** * Validate the declaration of prediction models according the syntax expected in the component's db folder. * diff --git a/analytics/tests/manager_test.php b/analytics/tests/manager_test.php index 923ad4060e1..004b7de2327 100644 --- a/analytics/tests/manager_test.php +++ b/analytics/tests/manager_test.php @@ -172,6 +172,20 @@ class analytics_manager_testcase extends advanced_testcase { $this->assertSame([], \core_analytics\manager::load_default_models_for_component('foo_bar2776327736558')); } + /** + * Tests for the {@link \core_analytics\manager::load_default_models_for_all_components()} implementation. + */ + public function test_load_default_models_for_all_components() { + $this->resetAfterTest(); + + $models = \core_analytics\manager::load_default_models_for_all_components(); + + $this->assertTrue(is_array($models['core'])); + $this->assertNotEmpty($models['core']); + $this->assertNotEmpty($models['core'][0]['target']); + $this->assertNotEmpty($models['core'][0]['indicators']); + } + /** * Tests for the successful execution of the {@link \core_analytics\manager::validate_models_declaration()}. */ From 7dba0c27ade26dc9084de51025c9c49f2881713a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Thu, 28 Mar 2019 11:42:17 +0100 Subject: [PATCH 3/4] MDL-64777 analytics: Add method to get target and indicators instances This will be needed in some other places, too. --- analytics/classes/manager.php | 29 +++++++++++++++++++++-------- analytics/tests/manager_test.php | 15 +++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/analytics/classes/manager.php b/analytics/classes/manager.php index c80925379eb..6c407951218 100644 --- a/analytics/classes/manager.php +++ b/analytics/classes/manager.php @@ -784,14 +784,7 @@ class manager { */ 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; - } + list($target, $indicators) = static::get_declared_target_and_indicators_instances($definition); if (isset($definition['timesplitting'])) { $timesplitting = $definition['timesplitting']; @@ -817,4 +810,24 @@ class manager { public static function model_declaration_identifier(array $model) : string { return 'id'.sha1(serialize($model)); } + + /** + * Given a model definition, return actual target and indicators instances. + * + * @param array $definition See {@link self::validate_models_declaration()} for the syntax. + * @return array [0] => target instance, [1] => array of indicators instances + */ + public static function get_declared_target_and_indicators_instances(array $definition): array { + + $target = static::get_target($definition['target']); + + $indicators = []; + + foreach ($definition['indicators'] as $indicatorname) { + $indicator = static::get_indicator($indicatorname); + $indicators[$indicator->get_id()] = $indicator; + } + + return [$target, $indicators]; + } } diff --git a/analytics/tests/manager_test.php b/analytics/tests/manager_test.php index 004b7de2327..ceb57969b94 100644 --- a/analytics/tests/manager_test.php +++ b/analytics/tests/manager_test.php @@ -460,4 +460,19 @@ class analytics_manager_testcase extends advanced_testcase { clean_param(\core_analytics\manager::model_declaration_identifier(reset($noteaching3)), PARAM_ALPHANUM) ); } + + /** + * Tests for the {@link \core_analytics\manager::get_declared_target_and_indicators_instances()}. + */ + public function test_get_declared_target_and_indicators_instances() { + $this->resetAfterTest(); + + $definition = $this->load_models_from_fixture_file('no_teaching'); + + list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition[0]); + + $this->assertTrue($target instanceof \core_analytics\local\target\base); + $this->assertNotEmpty($indicators); + $this->assertContainsOnlyInstancesOf(\core_analytics\local\indicator\base::class, $indicators); + } } From 76ef6610bb320fa4a03c505437cc5160ba8f89ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Wed, 27 Mar 2019 16:37:01 +0100 Subject: [PATCH 4/4] MDL-64777 analytics: Add ability to restore missing default models The patch introduces a new page restoredefault.php that allows the user to select missing models to be restored. --- .../analytics/classes/output/models_list.php | 29 ++- .../classes/output/restorable_models.php | 142 +++++++++++ .../tool/analytics/lang/en/tool_analytics.php | 12 + admin/tool/analytics/restoredefault.php | 79 ++++++ .../analytics/templates/models_list.mustache | 5 +- .../templates/restorable_models.mustache | 225 ++++++++++++++++++ .../tests/behat/restoredefault.feature | 103 ++++++++ admin/tool/analytics/version.php | 4 +- 8 files changed, 593 insertions(+), 6 deletions(-) create mode 100644 admin/tool/analytics/classes/output/restorable_models.php create mode 100644 admin/tool/analytics/restoredefault.php create mode 100644 admin/tool/analytics/templates/restorable_models.mustache create mode 100644 admin/tool/analytics/tests/behat/restoredefault.feature diff --git a/admin/tool/analytics/classes/output/models_list.php b/admin/tool/analytics/classes/output/models_list.php index b0431a35da5..806d358e562 100644 --- a/admin/tool/analytics/classes/output/models_list.php +++ b/admin/tool/analytics/classes/output/models_list.php @@ -62,8 +62,33 @@ class models_list implements \renderable, \templatable { global $PAGE; $data = new \stdClass(); - $data->importmodelurl = new \moodle_url('/admin/tool/analytics/importmodel.php'); - $data->createmodelurl = new \moodle_url('/admin/tool/analytics/createmodel.php'); + + $newmodelmenu = new \action_menu(); + $newmodelmenu->set_menu_trigger(get_string('newmodel', 'tool_analytics'), 'btn btn-default'); + $newmodelmenu->set_alignment(\action_menu::TL, \action_menu::BL); + + $newmodelmenu->add(new \action_menu_link( + new \moodle_url('/admin/tool/analytics/createmodel.php'), + new \pix_icon('i/edit', ''), + get_string('createmodel', 'tool_analytics'), + false + )); + + $newmodelmenu->add(new \action_menu_link( + new \moodle_url('/admin/tool/analytics/importmodel.php'), + new \pix_icon('i/import', ''), + get_string('importmodel', 'tool_analytics'), + false + )); + + $newmodelmenu->add(new \action_menu_link( + new \moodle_url('/admin/tool/analytics/restoredefault.php'), + new \pix_icon('i/reload', ''), + get_string('restoredefault', 'tool_analytics'), + false + )); + + $data->newmodelmenu = $newmodelmenu->export_for_template($output); $onlycli = get_config('analytics', 'onlycli'); if ($onlycli === false) { diff --git a/admin/tool/analytics/classes/output/restorable_models.php b/admin/tool/analytics/classes/output/restorable_models.php new file mode 100644 index 00000000000..a4e345673c6 --- /dev/null +++ b/admin/tool/analytics/classes/output/restorable_models.php @@ -0,0 +1,142 @@ +. + +/** + * Provides {@link \tool_analytics\output\restorable_models} class. + * + * @package tool_analytics + * @category output + * @copyright 2019 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tool_analytics\output; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Represents the list of default models that can be eventually restored. + * + * @copyright 2019 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class restorable_models implements \renderable, \templatable { + + /** @var array */ + protected $models; + + /** + * Instantiate an object of this class. + * + * @param array $models List of models as returned by {@link \core_analytics\manager::load_default_models_for_all_components()} + */ + public function __construct(array $models) { + + $this->models = $models; + } + + /** + * Export the list of models to be rendered. + * + * @param renderer_base $output + * @return string + */ + public function export_for_template(\renderer_base $output) { + + $components = []; + + foreach ($this->models as $componentname => $modelslist) { + $component = [ + 'name' => $this->component_name($componentname), + 'component' => $componentname, + 'models' => [], + ]; + + foreach ($modelslist as $definition) { + list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition); + + if (\core_analytics\model::exists($target, $indicators)) { + continue; + } + + $targetnamelangstring = $target->get_name(); + + $model = [ + 'defid' => \core_analytics\manager::model_declaration_identifier($definition), + 'targetname' => $targetnamelangstring, + 'targetclass' => $definition['target'], + 'indicatorsnum' => count($definition['indicators']), + 'indicators' => [], + ]; + + if (get_string_manager()->string_exists($targetnamelangstring->get_identifier().'_help', + $targetnamelangstring->get_component())) { + $helpicon = new \help_icon($targetnamelangstring->get_identifier(), $targetnamelangstring->get_component()); + $model['targethelp'] = $helpicon->export_for_template($output); + } + + foreach ($indicators as $indicator) { + $indicatornamelangstring = $indicator->get_name(); + $indicatordata = [ + 'name' => $indicatornamelangstring, + 'classname' => $indicator->get_id(), + ]; + + if (get_string_manager()->string_exists($indicatornamelangstring->get_identifier().'_help', + $indicatornamelangstring->get_component())) { + $helpicon = new \help_icon($indicatornamelangstring->get_identifier(), + $indicatornamelangstring->get_component()); + $indicatordata['indicatorhelp'] = $helpicon->export_for_template($output); + } + + $model['indicators'][] = $indicatordata; + } + + $component['models'][] = $model; + } + + if (!empty($component['models'])) { + $components[] = $component; + } + } + + $result = [ + 'hasdata' => !empty($components), + 'components' => array_values($components), + 'submiturl' => new \moodle_url('/admin/tool/analytics/restoredefault.php'), + 'backurl' => new \moodle_url('/admin/tool/analytics/index.php'), + 'sesskey' => sesskey(), + ]; + + return $result; + } + + /** + * Return a human readable name for the given frankenstyle component. + * + * @param string $component Frankenstyle component such as 'core', 'core_analytics' or 'mod_workshop' + * @return string Human readable name of the component + */ + protected function component_name(string $component): string { + + if ($component === 'core' || strpos($component, 'core_')) { + return get_string('componentcore', 'tool_analytics'); + + } else { + return get_string('pluginname', $component); + } + } +} diff --git a/admin/tool/analytics/lang/en/tool_analytics.php b/admin/tool/analytics/lang/en/tool_analytics.php index 34c1585ff71..b0cecaa978b 100644 --- a/admin/tool/analytics/lang/en/tool_analytics.php +++ b/admin/tool/analytics/lang/en/tool_analytics.php @@ -36,6 +36,10 @@ $string['clearmodelpredictions'] = 'Are you sure you want to clear all "{$a}" pr $string['clienablemodel'] = 'You can enable the model by selecting a time-splitting method by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).'; $string['clievaluationandpredictions'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. You can allow these processes to be executed manually via the web interface by disabling the \'onlycli\' analytics setting.'; $string['clievaluationandpredictionsnoadmin'] = 'A scheduled task iterates through enabled models and gets predictions. Models evaluation via the web interface is disabled. It may be enabled by a site administrator.'; +$string['component'] = 'Component'; +$string['componentcore'] = 'Core'; +$string['componentselect'] = 'Select all models provided by the component \'{$a}\''; +$string['componentselectnone'] = 'Unselect all'; $string['createmodel'] = 'Create model'; $string['currenttimesplitting'] = 'Current time-splitting method'; $string['delete'] = 'Delete'; @@ -81,6 +85,7 @@ $string['importmodel'] = 'Import model'; $string['indicators'] = 'Indicators'; $string['indicators_help'] = 'The indicators are what you think will lead to an accurate prediction of the target.'; $string['indicators_link'] = 'Indicators'; +$string['indicatorsnum'] = 'Number of indicators: {$a}'; $string['info'] = 'Info'; $string['ignoreversionmismatches'] = 'Ignore version mismatches'; $string['ignoreversionmismatchescheckbox'] = 'Ignore the differences between this site version and the original site version.'; @@ -98,6 +103,7 @@ $string['modelid'] = 'Model ID'; $string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model'; $string['modelresults'] = '{$a} results'; $string['modeltimesplitting'] = 'Time splitting'; +$string['newmodel'] = 'New model'; $string['nextpage'] = 'Next page'; $string['nodatatoevaluate'] = 'There is no data to evaluate the model'; $string['nodatatopredict'] = 'No new elements to get predictions for'; @@ -110,6 +116,12 @@ $string['predictmodels'] = 'Predict models'; $string['predictorresultsin'] = 'Predictor logged information in {$a} directory'; $string['predictionprocessfinished'] = 'Prediction process finished'; $string['previouspage'] = 'Previous page'; +$string['restoredefault'] = 'Restore default models'; +$string['restoredefaultempty'] = 'Please select models to be restored.'; +$string['restoredefaultinfo'] = 'These default models are missing or have changed since being installed. You can restore selected default models.'; +$string['restoredefaultnone'] = 'All default models provided by the Moodle core and installed plugins have been already created. No new models were found, there is nothing to restore.'; +$string['restoredefaultsome'] = 'Succesfully re-created {$a->count} new model(s).'; +$string['restoredefaultsubmit'] = 'Restore selected'; $string['samestartdate'] = 'Current start date is good'; $string['sameenddate'] = 'Current end date is good'; $string['selecttimesplittingforevaluation'] = 'Select the time-splitting method you want to use to evaluate the model configuration.'; diff --git a/admin/tool/analytics/restoredefault.php b/admin/tool/analytics/restoredefault.php new file mode 100644 index 00000000000..28b1b02071c --- /dev/null +++ b/admin/tool/analytics/restoredefault.php @@ -0,0 +1,79 @@ +. + +/** + * Check and create missing default prediction models. + * + * @package tool_analytics + * @copyright 2019 David Mudrák + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../../config.php'); + +require_login(); +\core_analytics\manager::check_can_manage_models(); + +$confirmed = optional_param('confirmed', false, PARAM_BOOL); +$restoreids = optional_param_array('restoreid', [], PARAM_ALPHANUM); + +$returnurl = new \moodle_url('/admin/tool/analytics/index.php'); +$myurl = new \moodle_url('/admin/tool/analytics/restoredefault.php'); + +\tool_analytics\output\helper::set_navbar(get_string('restoredefault', 'tool_analytics'), $myurl); + +if (data_submitted()) { + require_sesskey(); + + if (empty($restoreids)) { + $message = get_string('restoredefaultempty', 'tool_analytics'); + $type = \core\output\notification::NOTIFY_WARNING; + redirect($myurl, $message, null, $type); + } + + $numcreated = 0; + + foreach (\core_analytics\manager::load_default_models_for_all_components() as $componentname => $modelslist) { + foreach ($modelslist as $definition) { + if (!in_array(\core_analytics\manager::model_declaration_identifier($definition), $restoreids)) { + // This model has not been selected by the user. + continue; + } + + list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition); + + if (\core_analytics\model::exists($target, $indicators)) { + // This model exists (normally this should not happen as we do not show such models in the UI to select). + continue; + } + + \core_analytics\manager::create_declared_model($definition); + $numcreated++; + } + } + + $message = get_string('restoredefaultsome', 'tool_analytics', ['count' => $numcreated]); + $type = \core\output\notification::NOTIFY_SUCCESS; + + redirect($returnurl, $message, null, $type); +} + +$models = \core_analytics\manager::load_default_models_for_all_components(); +$ui = new \tool_analytics\output\restorable_models($models); + +echo $OUTPUT->header(); +echo $PAGE->get_renderer('tool_analytics')->render($ui); +echo $OUTPUT->footer(); diff --git a/admin/tool/analytics/templates/models_list.mustache b/admin/tool/analytics/templates/models_list.mustache index b49c9e212cd..c8cf2ae2e07 100644 --- a/admin/tool/analytics/templates/models_list.mustache +++ b/admin/tool/analytics/templates/models_list.mustache @@ -111,8 +111,9 @@
- {{#str}}createmodel, tool_analytics{{/str}} - {{#str}}importmodel, tool_analytics{{/str}} + {{#newmodelmenu}} + {{>core/action_menu}} + {{/newmodelmenu}}
diff --git a/admin/tool/analytics/templates/restorable_models.mustache b/admin/tool/analytics/templates/restorable_models.mustache new file mode 100644 index 00000000000..da85f7bdf67 --- /dev/null +++ b/admin/tool/analytics/templates/restorable_models.mustache @@ -0,0 +1,225 @@ +{{! + 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 tool_analytics/restorable_models + + Displays the list of missing prediction models that can be restored. + + Classes required for JS: + * The list should be wrapped within a id="restorablemodelslist" element. + + Data attributes required for JS: + * [data-widget="toggle"] indicates the clickable element for expanding/collapsing + the list of indicators used by the given model. + * [data-select] indicates a clickable element used for selecting multiple checkboxes. + * [data-component] should be set for checkboxes that select the particular model. + + Context variables required for this template: + * hasdata: boolean - do we have data to display + * submiturl: string - URL where the form should be submitted + * backurl: string - URL where the user should be sent without making any changes + * sesskey: string + * components: array - list of components to display + - name: string - human readable name of the component + - component: string - frankenstyle name of the component + - models: array - list of restorable models provided by the component + + defid: string - model definition identifier + + targetname: string - human readable name of the target + + targetclass: string - fully qualified classname of the target + + indicatorsnum: int - number of indicators + + indicators: array - list of indicators + ~ name: string - human readable name of the indicator + ~ classname: string - fully qualified classname of the indicator + + Example context (json): + { + "hasdata": true, + "submiturl": "https://example.com/moodle/admin/tool/analytics/restoredefault.php", + "backurl": "https://example.com/moodle/admin/tool/analytics/index.php", + "sesskey": "abcdefg123456", + "components": [ + { + "name": "Core", + "component": "core", + "models": [ + { + "defid": "id24680aceg", + "targetname": "No teaching", + "targetclass": "\\core\\analytics\\target\\no_teaching", + "indicatorsnum": 2, + "indicators": [ + { + "name": "There are no teachers", + "classname": "\\core\\analytics\\indicator\\no_teacher" + }, + { + "name": "There are no students", + "classname": "\\core\\analytics\\indicator\\no_students" + } + ] + }, + { + "defid": "id13579bdfi", + "targetname": "Students at risk of dropping out", + "targetclass": "\\core\\analytics\\target\\course_dropout", + "indicatorsnum": 1, + "indicators": [ + { + "name": "Read actions amount", + "classname": "\\core\\analytics\\indicator\\read_actions" + } + ] + } + ] + }, + { + "name": "Custom analytics plugin", + "component": "tool_customanalytics", + "models": [ + { + "defid": "id566dsgffg655", + "targetname": "Cheater", + "targetclass": "\\tool_customanalytics\\analytics\\target\\cheater", + "indicatorsnum": 1, + "indicators": [ + { + "name": "Copy-pasted submissions", + "classname": "\\tool_customanalytics\\analytics\\indicator\\copy_paster_submissions" + } + ] + } + ] + } + ] + } +}} +
+ {{^hasdata}} +

{{#str}} restoredefaultnone, tool_analytics {{/str}}

+ + {{/hasdata}} + + {{#hasdata}} +

{{#str}} restoredefaultinfo, tool_analytics {{/str}}

+
+
{{#str}}analyticmodels, tool_analytics{{/str}}
+ + + + + + + + + + + + + + {{#components}} + + + + {{#models}} + + + + + + {{/models}} + {{/components}} + +
{{#str}} selectall {{/str}}{{#str}} target, tool_analytics {{/str}}{{#str}} indicators, tool_analytics {{/str}}
+ + + {{name}} + + +
{{component}}
+
+ + + {{targetname}} + {{#targethelp}} + {{>core/help_icon}} + {{/targethelp}} +
{{targetclass}}
+
+ + +
+ + + {{/hasdata}} +
+ +{{#js}} +require(['jquery'], function($) { + + // Toggle the visibility of the indicators list. + $('#restorablemodelslist').on('click', '[data-widget="toggle"]', function(e) { + e.preventDefault(); + var toggle = $(e.currentTarget); + var listid = toggle.attr('aria-controls'); + + $(document.getElementById(listid)).toggle(); + + if (toggle.attr('aria-expanded') == 'false') { + toggle.attr('aria-expanded', 'true'); + } else { + toggle.attr('aria-expanded', 'false'); + } + }); + + // Selecting all / all in component checkboxes. + $('#restorablemodelslist').on('click', '[data-select]', function(e) { + e.preventDefault(); + var handler = $(e.currentTarget); + var component = handler.attr('data-select'); + + if (component == '*') { + $('input[data-component]').prop('checked', true); + } else { + $('input[data-component="' + component + '"]').prop('checked', true); + } + }); +}); +{{/js}} diff --git a/admin/tool/analytics/tests/behat/restoredefault.feature b/admin/tool/analytics/tests/behat/restoredefault.feature new file mode 100644 index 00000000000..74ff2397702 --- /dev/null +++ b/admin/tool/analytics/tests/behat/restoredefault.feature @@ -0,0 +1,103 @@ +@tool @tool_analytics +Feature: Restoring default models + In order to get prediction models into their initial state + As a manager + I need to be able to restore deleted default models + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | manager | Max | Manager | man@example.com | + And the following "role assigns" exist: + | user | role | contextlevel | reference | + | manager | manager | System | | + + Scenario: Restore a single deleted default model + Given I log in as "manager" + And I navigate to "Analytics > Analytics models" in site administration + # Delete 'No teaching' model. + And I click on "Delete" "link" in the "No teaching" "table_row" + And I should see "Analytics models" + And I should not see "No teaching" + # Delete 'Students at risk of dropping out' model. + And I click on "Delete" "link" in the "Students at risk of dropping out" "table_row" + And I should see "Analytics models" + And I should not see "Students at risk of dropping out" + # Go to the page for restoring deleted models. + When I click on "Restore default models" "link" + And I should see "No teaching" + And I should see "Students at risk of dropping out" + # Select and restore the 'No teaching' model. + And I set the field with xpath "//tr[contains(normalize-space(.), 'No teaching')]//input[@type='checkbox']" to "1" + And I click on "Restore selected" "button" + Then I should see "Succesfully re-created 1 new model(s)." + And I should see "Analytics models" + And I should see "No teaching" + And I should not see "Students at risk of dropping out" + + Scenario: Restore multiple deleted default models at once + Given I log in as "manager" + And I navigate to "Analytics > Analytics models" in site administration + # Delete 'No teaching' model. + And I click on "Delete" "link" in the "No teaching" "table_row" + And I should see "Analytics models" + And I should not see "No teaching" + # Delete 'Students at risk of dropping out' model. + And I click on "Delete" "link" in the "Students at risk of dropping out" "table_row" + And I should see "Analytics models" + And I should not see "Students at risk of dropping out" + # Go to the page for restoring deleted models. + When I click on "Restore default models" "link" + And I should see "No teaching" + And I should see "Students at risk of dropping out" + # Select and restore both models. + And I set the field with xpath "//tr[contains(normalize-space(.), 'No teaching')]//input[@type='checkbox']" to "1" + And I set the field with xpath "//tr[contains(normalize-space(.), 'Students at risk of dropping out')]//input[@type='checkbox']" to "1" + And I click on "Restore selected" "button" + Then I should see "Succesfully re-created 2 new model(s)." + And I should see "Analytics models" + And I should see "No teaching" + And I should see "Students at risk of dropping out" + + Scenario: Going to the restore page while no models can be restored + Given I log in as "manager" + And I navigate to "Analytics > Analytics models" in site administration + And I should see "Analytics models" + And I should see "No teaching" + When I click on "Restore default models" "link" + Then I should see "All default models provided by the Moodle core and installed plugins have been already created. No new models were found, there is nothing to restore." + And I click on "Back" "link" + And I should see "Analytics models" + + @javascript + Scenario: User can select and restore all missing models + Given I log in as "manager" + And I navigate to "Analytics > Analytics models" in site administration + # Delete 'No teaching' model. + And I click on "Actions" "link" in the "No teaching" "table_row" + And I click on "Delete" "link" in the "No teaching" "table_row" + And I click on "Delete" "button" in the "Delete" "dialogue" + And I should see "Analytics models" + And I should not see "No teaching" + # Delete 'Students at risk of dropping out' model. + And I click on "Actions" "link" in the "Students at risk of dropping out" "table_row" + And I click on "Delete" "link" in the "Students at risk of dropping out" "table_row" + And I click on "Delete" "button" in the "Delete" "dialogue" + And I should see "Analytics models" + And I should not see "No teaching" + And I should not see "Students at risk of dropping out" + # Go to the page for restoring deleted models. + And I click on "New model" "link" + And I click on "Restore default models" "link" + And I should see "No teaching" + And I should see "Students at risk of dropping out" + # Attempt to submit the form without selecting any model. + And I click on "Restore selected" "button" + And I should see "Please select models to be restored." + # Select all models. + When I click on "Select all" "link" + And I click on "Restore selected" "button" + Then I should see "Succesfully re-created 2 new model(s)." + And I should see "Analytics models" + And I should see "No teaching" + And I should see "Students at risk of dropping out" diff --git a/admin/tool/analytics/version.php b/admin/tool/analytics/version.php index c3d98139f9a..fae12cb2250 100644 --- a/admin/tool/analytics/version.php +++ b/admin/tool/analytics/version.php @@ -24,6 +24,6 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018120300; // The current plugin version (Date: YYYYMMDDXX). -$plugin->requires = 2018112800; // Requires this Moodle version. +$plugin->version = 2019032800; // The current plugin version (Date: YYYYMMDDXX). +$plugin->requires = 2019032200; // Requires this Moodle version. $plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).