From b0fb8ef59fb0fd3928fad44eff11e2aea0a12ef1 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 6 Oct 2017 17:37:43 +0200 Subject: [PATCH 1/4] MDL-59063 analytics: Expand analysable API to provide a name --- analytics/classes/analysable.php | 7 +++++++ analytics/classes/course.php | 9 +++++++++ analytics/classes/local/analyser/base.php | 4 ++-- analytics/classes/site.php | 9 +++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/analytics/classes/analysable.php b/analytics/classes/analysable.php index 88d10a6bf96..97faf596e8e 100644 --- a/analytics/classes/analysable.php +++ b/analytics/classes/analysable.php @@ -47,6 +47,13 @@ interface analysable { */ public function get_id(); + /** + * The analysable human readable name + * + * @return string + */ + public function get_name(); + /** * The analysable context. * diff --git a/analytics/classes/course.php b/analytics/classes/course.php index 791d7c2daef..07c5e9d5ef7 100644 --- a/analytics/classes/course.php +++ b/analytics/classes/course.php @@ -195,6 +195,15 @@ class course implements \core_analytics\analysable { return $this->course->id; } + /** + * The course short name + * + * @return string + */ + public function get_name() { + return format_string($this->course->shortname, true, array('context' => $this->get_context())); + } + /** * get_context * diff --git a/analytics/classes/local/analyser/base.php b/analytics/classes/local/analyser/base.php index 8d792b48ddd..a8277e5784c 100644 --- a/analytics/classes/local/analyser/base.php +++ b/analytics/classes/local/analyser/base.php @@ -363,7 +363,7 @@ abstract class base { $result = $this->analysabletarget->is_valid_analysable($analysable, $includetarget); if ($result !== true) { $a = new \stdClass(); - $a->analysableid = $analysable->get_id(); + $a->analysableid = $analysable->get_name(); $a->result = $result; $this->add_log(get_string('analysablenotvalidfortarget', 'analytics', $a)); return array(); @@ -407,7 +407,7 @@ abstract class base { } $a = new \stdClass(); - $a->analysableid = $analysable->get_id(); + $a->analysableid = $analysable->get_name(); $a->errors = implode(', ', $errors); $this->add_log(get_string('analysablenotused', 'analytics', $a)); } diff --git a/analytics/classes/site.php b/analytics/classes/site.php index 69fe7220331..c503e8aeeff 100644 --- a/analytics/classes/site.php +++ b/analytics/classes/site.php @@ -54,6 +54,15 @@ class site implements \core_analytics\analysable { return SYSCONTEXTID; } + /** + * Site. + * + * @return string + */ + public function get_name() { + return get_string('site'); + } + /** * Analysable context. * From 019ad9c7a80c6c7d74f6f0ff38e042fd8a79cbfc Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 6 Oct 2017 17:38:01 +0200 Subject: [PATCH 2/4] MDL-59063 tool_analytics: New invalid analysables report --- .../classes/output/invalid_analysables.php | 158 ++++++++++++++++++ .../analytics/classes/output/models_list.php | 10 ++ .../analytics/classes/output/renderer.php | 11 ++ .../tool/analytics/lang/en/tool_analytics.php | 8 + admin/tool/analytics/model.php | 17 ++ .../templates/invalid_analysables.mustache | 78 +++++++++ lang/en/moodle.php | 4 +- 7 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 admin/tool/analytics/classes/output/invalid_analysables.php create mode 100644 admin/tool/analytics/templates/invalid_analysables.mustache diff --git a/admin/tool/analytics/classes/output/invalid_analysables.php b/admin/tool/analytics/classes/output/invalid_analysables.php new file mode 100644 index 00000000000..35a6f90962b --- /dev/null +++ b/admin/tool/analytics/classes/output/invalid_analysables.php @@ -0,0 +1,158 @@ +. + +/** + * Invalid analysables renderable. + * + * @package tool_analytics + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tool_analytics\output; + +defined('MOODLE_INTERNAL') || die; + +/** + * Invalid analysables renderable. + * + * @package tool_analytics + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class invalid_analysables implements \renderable, \templatable { + + /** + * @var \core_analytics\model + */ + protected $model = null; + + /** + * @var int + */ + protected $page = 0; + + /** + * @var int + */ + protected $perpage = 0; + + /** + * Inits the invalid analysables renderable. + * + * @param \core_analytics\model $model + * @param int $page + * @param int $perpage + * @return \stdClass + */ + public function __construct(\core_analytics\model $model, $page, $perpage) { + + $this->model = $model; + $this->page = $page; + $this->perpage = $perpage; + } + + /** + * Export the data. + * + * @param \renderer_base $output + * @return \stdClass + */ + public function export_for_template(\renderer_base $output) { + global $PAGE; + + $offset = $this->page * $this->perpage; + + $analysables = $this->model->get_analyser(['evaluation' => true])->get_analysables(); + + $skipped = 0; + $enoughresults = false; + $morepages = false; + $results = array(); + foreach ($analysables as $key => $analysable) { + + $validtraining = $this->model->get_target()->is_valid_analysable($analysable, true); + if ($validtraining === true) { + if ($this->model->is_static()) { + // We still want to show this analysable if it is not valid to get predictions. + $validtraining = get_string('notrainingbasedassumptions', 'analytics'); + } else { + // We skip analysables that are valid for training or valid for prediction. + continue; + } + } + + $validprediction = $this->model->get_target()->is_valid_analysable($analysable, false); + if ($validprediction === true) { + // We skip analysables that are valid for training or valid for prediction. + continue; + } + + if ($offset && $skipped < $offset) { + $skipped++; + continue; + } + + // Add a new results if we don't have enough yet. + if (!$enoughresults) { + $results[$analysable->get_id()] = array($analysable, $validtraining, $validprediction); + if ($this->perpage && count($results) === $this->perpage) { + $enoughresults = true; + } + } else { + // Confirmed that we have results we can not fit into this page. + $morepages = true; + break; + } + + unset($analysables[$key]); + } + + // Prepare the context object. + $data = new \stdClass(); + $data->modelname = $this->model->get_target()->get_name(); + + if ($this->page > 0) { + $prev = clone $PAGE->url; + $prev->param('page', $this->page - 1); + $button = new \single_button($prev, get_string('previouspage', 'tool_analytics'), 'get'); + $data->prev = $button->export_for_template($output); + } + if ($morepages) { + $next = clone $PAGE->url; + $next->param('page', $this->page + 1); + $button = new \single_button($next, get_string('nextpage', 'tool_analytics'), 'get'); + $data->next = $button->export_for_template($output); + } + + $data->analysables = []; + foreach ($results as list($analysable, $validtraining, $validprediction)) { + $obj = new \stdClass(); + $obj->url = \html_writer::link($analysable->get_context()->get_url(), $analysable->get_name(), + array('target' => '_blank')); + + if ($validtraining !== true) { + $obj->validtraining = $validtraining; + } + if ($validprediction !== true) { + $obj->validprediction = $validprediction; + } + $data->analysables[] = $obj; + } + + return $data; + } +} diff --git a/admin/tool/analytics/classes/output/models_list.php b/admin/tool/analytics/classes/output/models_list.php index cc499ef5345..ad326a6ecf7 100644 --- a/admin/tool/analytics/classes/output/models_list.php +++ b/admin/tool/analytics/classes/output/models_list.php @@ -230,6 +230,16 @@ class models_list implements \renderable, \templatable { $actionsmenu->add($icon); } + // Invalid analysables. + $analyser = $model->get_analyser(); + if (!$analyser instanceof \core_analytics\local\analyser\sitewide) { + $urlparams['action'] = 'invalidanalysables'; + $url = new \moodle_url('model.php', $urlparams); + $pix = new \pix_icon('i/report', get_string('invalidanalysables', 'tool_analytics')); + $icon = new \action_menu_link_secondary($url, $pix, get_string('invalidanalysables', 'tool_analytics')); + $actionsmenu->add($icon); + } + // Clear model. if (!empty($predictioncontexts)) { $actionid = 'clear-' . $model->get_id(); diff --git a/admin/tool/analytics/classes/output/renderer.php b/admin/tool/analytics/classes/output/renderer.php index 628b097f264..4201482df14 100644 --- a/admin/tool/analytics/classes/output/renderer.php +++ b/admin/tool/analytics/classes/output/renderer.php @@ -207,4 +207,15 @@ class renderer extends plugin_renderer_base { return $output; } + + /** + * Defer to template. + * + * @param \tool_analytics\output\invalid_analysables $invalidanalysables + * @return string HTML + */ + protected function render_invalid_analysables(\tool_analytics\output\invalid_analysables $invalidanalysables) { + $data = $invalidanalysables->export_for_template($this); + return parent::render_from_template('tool_analytics/invalid_analysables', $data); + } } diff --git a/admin/tool/analytics/lang/en/tool_analytics.php b/admin/tool/analytics/lang/en/tool_analytics.php index e1362fdca97..c4f0e5ae4f5 100644 --- a/admin/tool/analytics/lang/en/tool_analytics.php +++ b/admin/tool/analytics/lang/en/tool_analytics.php @@ -59,9 +59,16 @@ $string['goodmodel'] = 'This is a good model for using to obtain predictions. En $string['indicators'] = 'Indicators'; $string['info'] = 'Info'; $string['insights'] = 'Insights'; +$string['invalidanalysables'] = 'Invalid site elements'; +$string['invalidanalysablesinfo'] = 'This pages lists this site analysable elements that can not be used by this prediction model. The listed elements can not be used neither to train the prediction model nor the prediction model can get predictions for them.'; +$string['invalidanalysablestable'] = 'Invalid site analysable elements table'; +$string['invalidprediction'] = 'Invalid to get predictions'; +$string['invalidtraining'] = 'Invalid to train the model'; $string['loginfo'] = 'Log extra info'; +$string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model'; $string['modelresults'] = '{$a} results'; $string['modeltimesplitting'] = 'Time splitting'; +$string['nextpage'] = 'Next page'; $string['nodatatoevaluate'] = 'There is no data to evaluate the model'; $string['nodatatopredict'] = 'No new elements to get predictions for'; $string['nodatatotrain'] = 'There is no new data that can be used for training'; @@ -71,6 +78,7 @@ $string['predictionresults'] = 'Prediction results'; $string['predictmodels'] = 'Predict models'; $string['predictorresultsin'] = 'Predictor logged information in {$a} directory'; $string['predictionprocessfinished'] = 'Prediction process finished'; +$string['previouspage'] = 'Previous page'; $string['samestartdate'] = 'Current start date is good'; $string['sameenddate'] = 'Current end date is good'; $string['target'] = 'Target'; diff --git a/admin/tool/analytics/model.php b/admin/tool/analytics/model.php index e418b4656bb..58f11292bd4 100644 --- a/admin/tool/analytics/model.php +++ b/admin/tool/analytics/model.php @@ -63,6 +63,9 @@ switch ($action) { case 'clear': $title = get_string('clearpredictions', 'tool_analytics'); break; + case 'invalidanalysables': + $title = get_string('invalidanalysables', 'tool_analytics'); + break; default: throw new moodle_exception('errorunknownaction', 'analytics'); } @@ -219,6 +222,20 @@ switch ($action) { $model->clear(); redirect(new \moodle_url('/admin/tool/analytics/index.php')); break; + + case 'invalidanalysables': + + echo $OUTPUT->header(); + + $page = optional_param('page', 0, PARAM_INT); + // No option in the UI to change this, only for url hackers ;). + $perpage = optional_param('perpage', 10, PARAM_INT); + + $renderable = new \tool_analytics\output\invalid_analysables($model, $page, $perpage); + $renderer = $PAGE->get_renderer('tool_analytics'); + echo $renderer->render($renderable); + + break; } echo $OUTPUT->footer(); diff --git a/admin/tool/analytics/templates/invalid_analysables.mustache b/admin/tool/analytics/templates/invalid_analysables.mustache new file mode 100644 index 00000000000..c97dd6be309 --- /dev/null +++ b/admin/tool/analytics/templates/invalid_analysables.mustache @@ -0,0 +1,78 @@ +{{! + 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/invalid_analysables + + Template for invalid analysables. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * none + + Example context (json): + { + "modelname": "Not engaging courses", + "analysables": [ + { + "url": "Maths", + "validtraining": "Ongoing course", + "validprediction": "Not enough students activity" + }, { + "url": "Psichology", + "validtraining": "No students", + "validprediction": "No students" + } + ] + } +}} + +
+

{{#str}}modelinvalidanalysables, tool_analytics, {{modelname}}{{/str}}

+
{{#str}}invalidanalysablesinfo, tool_analytics{{/str}}
+
+ {{#prev}}{{> core/single_button}}{{/prev}} + {{#next}}{{> core/single_button}}{{/next}} +
+ + + + + + + + + + + {{#analysables}} + + + + + + {{/analysables}} + +
{{#str}}invalidanalysablestable, tool_analytics{{/str}}
{{#str}}name{{/str}}{{#str}}invalidtraining, tool_analytics{{/str}}{{#str}}invalidprediction, tool_analytics{{/str}}
{{{url}}}{{validtraining}}{{validprediction}}
+
+ {{#prev}}{{> core/single_button}}{{/prev}} + {{#next}}{{> core/single_button}}{{/next}} +
+
diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 6844d5fd3a8..e36d9704e5a 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -336,8 +336,8 @@ $string['coursehelpnewsitemsnumber'] = 'Number of recent announcements appearing $string['coursehelpnumberweeks'] = 'Number of sections in the course (applies to certain course formats only).'; $string['coursehelpshowgrades'] = 'Enable the display of the gradebook. It does not prevent grades from being displayed within the individual activities.'; $string['coursehidden'] = 'This course is currently unavailable to students'; -$string['coursenotyetstarted'] = 'The course is not yet started'; -$string['coursenotyetfinished'] = 'The course is not yet finished'; +$string['coursenotyetstarted'] = 'The course has not yet started'; +$string['coursenotyetfinished'] = 'The course has not yet finished'; $string['courseoverviewfiles'] = 'Course summary files'; $string['courseoverviewfilesext'] = 'Course summary files extensions'; $string['courseoverviewfileslimit'] = 'Course summary files limit'; From fefa34120a6e333019b2548ca464f7d35e8eab45 Mon Sep 17 00:00:00 2001 From: David Monllao Date: Fri, 6 Oct 2017 18:38:10 +0200 Subject: [PATCH 3/4] MDL-59063 analytics: Finished courses can't be used for prediction --- lang/en/moodle.php | 1 + lib/classes/analytics/target/course_dropout.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/lang/en/moodle.php b/lang/en/moodle.php index e36d9704e5a..4ce65d91b1f 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -292,6 +292,7 @@ $string['counteditems'] = '{$a->count} {$a->items}'; $string['country'] = 'Country'; $string['course'] = 'Course'; $string['courseadministration'] = 'Course administration'; +$string['coursealreadyfinished'] = 'Course already finished'; $string['courseapprovedemail'] = 'Your requested course, {$a->name}, has been approved and you have been made a {$a->teacher}. To access your new course, go to {$a->url}'; $string['courseapprovedemail2'] = 'Your requested course, {$a->name}, has been approved. To access your new course, go to {$a->url}'; $string['courseapprovedfailed'] = 'Failed to save the course as approved!'; diff --git a/lib/classes/analytics/target/course_dropout.php b/lib/classes/analytics/target/course_dropout.php index 9787b815d97..b44ec4d34ae 100644 --- a/lib/classes/analytics/target/course_dropout.php +++ b/lib/classes/analytics/target/course_dropout.php @@ -158,6 +158,11 @@ class course_dropout extends \core_analytics\local\target\binary { return get_string('coursetoolong', 'analytics'); } + // Finished courses can not be used to get predictions. + if (!$fortraining && $course->is_finished()) { + return get_string('coursealreadyfinished'); + } + // Ongoing courses data can not be used to train. if ($fortraining && !$course->is_finished()) { return get_string('coursenotyetfinished'); From 71339af8c61dd9fef92c0043b6d2c35addbdd6ae Mon Sep 17 00:00:00 2001 From: David Monllao Date: Tue, 10 Oct 2017 17:14:34 +0200 Subject: [PATCH 4/4] MDL-59063 analytics: No need to load timesplitting method --- admin/tool/analytics/classes/output/invalid_analysables.php | 2 +- admin/tool/analytics/classes/output/models_list.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/tool/analytics/classes/output/invalid_analysables.php b/admin/tool/analytics/classes/output/invalid_analysables.php index 35a6f90962b..a5bda0949fd 100644 --- a/admin/tool/analytics/classes/output/invalid_analysables.php +++ b/admin/tool/analytics/classes/output/invalid_analysables.php @@ -76,7 +76,7 @@ class invalid_analysables implements \renderable, \templatable { $offset = $this->page * $this->perpage; - $analysables = $this->model->get_analyser(['evaluation' => true])->get_analysables(); + $analysables = $this->model->get_analyser(['notimesplitting' => true])->get_analysables(); $skipped = 0; $enoughresults = false; diff --git a/admin/tool/analytics/classes/output/models_list.php b/admin/tool/analytics/classes/output/models_list.php index ad326a6ecf7..f33980cafeb 100644 --- a/admin/tool/analytics/classes/output/models_list.php +++ b/admin/tool/analytics/classes/output/models_list.php @@ -231,7 +231,7 @@ class models_list implements \renderable, \templatable { } // Invalid analysables. - $analyser = $model->get_analyser(); + $analyser = $model->get_analyser(['notimesplitting' => true]); if (!$analyser instanceof \core_analytics\local\analyser\sitewide) { $urlparams['action'] = 'invalidanalysables'; $url = new \moodle_url('model.php', $urlparams);