diff --git a/admin/tool/analytics/lang/en/tool_analytics.php b/admin/tool/analytics/lang/en/tool_analytics.php index df38a14e7ae..3cdb7232e9e 100644 --- a/admin/tool/analytics/lang/en/tool_analytics.php +++ b/admin/tool/analytics/lang/en/tool_analytics.php @@ -30,7 +30,7 @@ $string['bettercli'] = 'Evaluating models and generating predictions may involve $string['cantguessstartdate'] = 'Can\'t guess the start date'; $string['cantguessenddate'] = 'Can\'t guess the end date'; $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 cron task iterates through enabled models and gets predictions. Models evaluation via command line is disabled. You can allow these processes to be executed manually via web interface by enabling \'onlycli\' analytics setting'; +$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['editmodel'] = 'Edit "{$a}" model'; $string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its time-splitting method will delete its previous predictions and start generating new predictions.'; $string['enabled'] = 'Enabled'; diff --git a/analytics/classes/model.php b/analytics/classes/model.php index 7d9c2bd2f45..44b9e1e8953 100644 --- a/analytics/classes/model.php +++ b/analytics/classes/model.php @@ -932,8 +932,6 @@ class model { * @return \context */ protected function prepare_prediction_record($sampleid, $rangeindex, $prediction, $predictionscore, $calculations) { - global $DB; - $context = $this->get_analyser()->sample_access_context($sampleid); $record = new \stdClass(); @@ -946,6 +944,15 @@ class model { $record->calculations = $calculations; $record->timecreated = time(); + $analysable = $this->get_analyser()->get_sample_analysable($sampleid); + $timesplitting = $this->get_time_splitting(); + $timesplitting->set_analysable($analysable); + $range = $timesplitting->get_range_by_index($rangeindex); + if ($range) { + $record->timestart = $range['start']; + $record->timeend = $range['end']; + } + return array($record, $context); } diff --git a/lib/db/install.xml b/lib/db/install.xml index 99f8570f454..caebf171195 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -3623,6 +3623,8 @@ + + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 92a78388474..97acd71aec4 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -2676,6 +2676,28 @@ function xmldb_main_upgrade($oldversion) { // Main savepoint reached. upgrade_main_savepoint(true, 2017101000.01); + } + + if ($oldversion < 2017101000.02) { + // Define field 'timestart' to be added to 'analytics_predictions'. + $table = new xmldb_table('analytics_predictions'); + $field = new xmldb_field('timestart', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'timecreated'); + + // Conditionally launch add field 'timestart'. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field 'timeend' to be added to 'analytics_predictions'. + $field = new xmldb_field('timeend', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'timestart'); + + // Conditionally launch add field 'timeend'. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2017101000.02); } return true; diff --git a/report/insights/classes/output/insight.php b/report/insights/classes/output/insight.php index 525129fd8f0..d0ffa6e2114 100644 --- a/report/insights/classes/output/insight.php +++ b/report/insights/classes/output/insight.php @@ -71,10 +71,23 @@ class insight implements \renderable, \templatable { * @return \stdClass */ public function export_for_template(\renderer_base $output) { + // Get the prediction data. + $predictiondata = $this->prediction->get_prediction_data(); $data = new \stdClass(); $data->insightname = format_string($this->model->get_target()->get_name()); + // Get the details. + $data->timecreated = userdate($predictiondata->timecreated); + $data->timerange = ''; + + if (!empty($predictiondata->timestart) && !empty($predictiondata->timeend)) { + $timerange = new \stdClass(); + $timerange->timestart = userdate($predictiondata->timestart); + $timerange->timeend = userdate($predictiondata->timeend); + $data->timerange = get_string('timerangewithdata', 'report_insights', $timerange); + } + // Sample info (determined by the analyser). list($data->sampledescription, $samplerenderable) = $this->model->prediction_sample_description($this->prediction); @@ -84,10 +97,10 @@ class insight implements \renderable, \templatable { } // Prediction info. - $predictedvalue = $this->prediction->get_prediction_data()->prediction; - $predictionid = $this->prediction->get_prediction_data()->id; + $predictedvalue = $predictiondata->prediction; + $predictionid = $predictiondata->id; $data->predictiondisplayvalue = $this->model->get_target()->get_display_value($predictedvalue); - list($data->style, $data->outcomeicon) = $this->get_calculation_display($this->model->get_target(), + list($data->style, $data->outcomeicon) = self::get_calculation_display($this->model->get_target(), floatval($predictedvalue), $output); $actions = $this->model->get_target()->prediction_actions($this->prediction, $this->includedetailsaction); @@ -124,7 +137,7 @@ class insight implements \renderable, \templatable { $obj = new \stdClass(); $obj->name = call_user_func(array($calculation->indicator, 'get_name')); $obj->displayvalue = $calculation->indicator->get_display_value($calculation->value, $calculation->subtype); - list($obj->style, $obj->outcomeicon) = $this->get_calculation_display($calculation->indicator, + list($obj->style, $obj->outcomeicon) = self::get_calculation_display($calculation->indicator, floatval($calculation->value), $output, $calculation->subtype); $data->calculations[] = $obj; @@ -149,7 +162,7 @@ class insight implements \renderable, \templatable { * @param string|false $subtype * @return array The style as 'success', 'info', 'warning' or 'danger' and pix_icon */ - protected function get_calculation_display(\core_analytics\calculable $calculable, $value, $output, $subtype = false) { + public static function get_calculation_display(\core_analytics\calculable $calculable, $value, $output, $subtype = false) { $outcome = $calculable->get_calculation_outcome($value, $subtype); switch ($outcome) { case \core_analytics\calculable::OUTCOME_NEUTRAL: diff --git a/report/insights/classes/output/insights_list.php b/report/insights/classes/output/insights_list.php index 1da683eb2b7..158e8bebb1c 100644 --- a/report/insights/classes/output/insights_list.php +++ b/report/insights/classes/output/insights_list.php @@ -95,17 +95,39 @@ class insights_list implements \renderable, \templatable { if ($this->model->uses_insights()) { $predictionsdata = $this->model->get_predictions($this->context, true, $this->page, $this->perpage); - $data->insights = array(); + $data->predictions = array(); + $predictionvalues = array(); + $insights = array(); if ($predictionsdata) { list($total, $predictions) = $predictionsdata; foreach ($predictions as $prediction) { + $predictedvalue = $prediction->get_prediction_data()->prediction; + + // Only need to fill this data once. + if (!isset($predictionvalues[$predictedvalue])) { + $preddata = array(); + $preddata['predictiondisplayvalue'] = $this->model->get_target()->get_display_value($predictedvalue); + list($preddata['style'], $preddata['outcomeicon']) = + insight::get_calculation_display($this->model->get_target(), $predictedvalue, $output); + $predictionvalues[$predictedvalue] = $preddata; + } + $insightrenderable = new \report_insights\output\insight($prediction, $this->model, true); - $data->insights[] = $insightrenderable->export_for_template($output); + $insights[$predictedvalue][] = $insightrenderable->export_for_template($output); + } + + // Ok, now we have all the data we want, put it into a format that mustache can handle. + foreach ($predictionvalues as $key => $prediction) { + if (isset($insights[$key])) { + $prediction['insights'] = $insights[$key]; + } + + $data->predictions[] = $prediction; } } - if (empty($data->insights) && $this->page == 0) { + if (empty($insights) && $this->page == 0) { if ($this->model->any_prediction_obtained()) { $data->noinsights = get_string('noinsights', 'analytics'); } else { diff --git a/report/insights/lang/en/report_insights.php b/report/insights/lang/en/report_insights.php index a69600b98cf..68827ab9b36 100644 --- a/report/insights/lang/en/report_insights.php +++ b/report/insights/lang/en/report_insights.php @@ -40,4 +40,7 @@ $string['prediction'] = 'Prediction'; $string['predictioncalculations'] = 'Indicator calculations'; $string['predictiondetails'] = 'Prediction details'; $string['nodetailsavailable'] = 'No prediction details are relevant.'; +$string['timecreated'] = 'Time predicted'; +$string['timerange'] = 'Time range'; +$string['timerangewithdata'] = '{$a->timestart} to {$a->timeend}'; $string['selectotherinsights'] = 'Select other insights...'; diff --git a/report/insights/templates/insight.mustache b/report/insights/templates/insight.mustache index ad24102fc3e..693549d7f50 100644 --- a/report/insights/templates/insight.mustache +++ b/report/insights/templates/insight.mustache @@ -31,29 +31,16 @@ Example context (json): { "sampleimage": "Link", - "sampledescription": "Sample description", - "style": "success", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "predictiondisplayvalue": "This dev will understand it" + "sampledescription": "Sample description" } }} - + {{#sampleimage}} {{{sampleimage}}} {{/sampleimage}} {{{sampledescription}}} - - {{#outcomeicon}} - {{> core/pix_icon}} - {{/outcomeicon}} - {{predictiondisplayvalue}} - {{#actions}} {{> core/action_menu}} diff --git a/report/insights/templates/insight_details.mustache b/report/insights/templates/insight_details.mustache index 72940c9e1fb..f5a6493d263 100644 --- a/report/insights/templates/insight_details.mustache +++ b/report/insights/templates/insight_details.mustache @@ -31,6 +31,8 @@ Example context (json): { "insightname": "Best insight ever", + "timecreated": "Thursday, 5 October 2017, 4:16 PM", + "timerange": "Monday, 4 September 2017, 6:00 PM to Thursday, 5 October 2017, 12:00 AM", "sampleimage": "Link", "sampledescription": "Sample description", "style": "success", @@ -73,11 +75,18 @@

{{#str}}insightprediction, report_insights, {{insightname}} {{/str}}

- + - - + @@ -86,8 +95,23 @@
{{#str}}insight, report_insights{{/str}} + {{#str}}prediction, report_insights{{/str}}: + + {{#outcomeicon}} + {{> core/pix_icon}} + {{/outcomeicon}} + {{predictiondisplayvalue}} + +
{{#str}}name{{/str}}{{#str}}prediction, report_insights{{/str}}{{#str}}name{{/str}} {{#str}}actions{{/str}}
- +
+ + + + + + {{#timerange}} + + + + + {{/timerange}} + +
{{#str}}predictiondetails, report_insights{{/str}}
{{#str}}timecreated, report_insights{{/str}}{{timecreated}}
{{#str}}timerange, report_insights{{/str}}{{.}}
+ + @@ -99,7 +123,7 @@ - + {{/calculations}}
{{#str}}predictioncalculations, report_insights{{/str}}
{{#str}}indicator, report_insights{{/str}}
{{name}} {{#outcomeicon}}{{> core/pix_icon}}{{/outcomeicon}} {{displayvalue}}
diff --git a/report/insights/templates/insights_list.mustache b/report/insights/templates/insights_list.mustache index ecb26899d65..2c6c2008252 100644 --- a/report/insights/templates/insights_list.mustache +++ b/report/insights/templates/insights_list.mustache @@ -31,27 +31,35 @@ Example context (json): { "insightname": "Best insight ever", - "insights": [ + "predictions": [ { - "sampleimage": "Link", - "sampledescription": "Sample description", + "predictiondisplayvalue": "This dev will understand it", "style": "success", "outcomeicon": { "attributes": [ {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } ] }, - "predictiondisplayvalue": "This dev will understand it" + "insights": [ + { + "sampleimage": "Link", + "sampledescription": "Sample description" + } + ] }, { - "sampleimage": "Any renderable", - "sampledescription": "Another sample description", + "predictiondisplayvalue": "This dev will not understand it", "style": "danger", "outcomeicon": { "attributes": [ {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } ] }, - "predictiondisplayvalue": "This dev will not understand it" + "insights": [ + { + "sampleimage": "Any renderable", + "sampledescription": "Another sample description" + } + ] } ], "noinsights": false @@ -67,21 +75,30 @@

{{{insightname}}}

{{^noinsights}} {{{ pagingbar }}} - - - - - - - - - - - {{#insights}} +{{#predictions}} +
{{#str}}insights, report_insights{{/str}}
{{#str}}name{{/str}}{{#str}}prediction, report_insights{{/str}}{{#str}}actions{{/str}}
+ + + + + + + + {{#insights}} + {{> report_insights/insight}} - {{/insights}} - -
+ {{#str}}prediction, report_insights{{/str}}: + + {{#outcomeicon}} + {{> core/pix_icon}} + {{/outcomeicon}} + {{predictiondisplayvalue}} + +
{{#str}}name{{/str}}{{#str}}actions{{/str}}
+ + {{/insights}} + +{{/predictions}} {{{ pagingbar }}} {{/noinsights}} {{#noinsights}} diff --git a/theme/bootstrapbase/templates/report_insights/insight.mustache b/theme/bootstrapbase/templates/report_insights/insight.mustache deleted file mode 100644 index 3e152a6cc38..00000000000 --- a/theme/bootstrapbase/templates/report_insights/insight.mustache +++ /dev/null @@ -1,62 +0,0 @@ -{{! - 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 report_insights/insight - - Template for a insight. - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * none - - Example context (json): - { - "sampleimage": "Link", - "sampledescription": "Sample description", - "style": "success", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "predictiondisplayvalue": "This dev will understand it" - } -}} - - - {{#sampleimage}} - {{{sampleimage}}} - {{/sampleimage}} - {{{sampledescription}}} - - - {{#outcomeicon}} - {{> core/pix_icon}} - {{/outcomeicon}} - {{predictiondisplayvalue}} - - - {{#actions}} - {{> core/action_menu}} - {{/actions}} - - diff --git a/theme/bootstrapbase/templates/report_insights/insight_details.mustache b/theme/bootstrapbase/templates/report_insights/insight_details.mustache deleted file mode 100644 index 65dda0fa0dd..00000000000 --- a/theme/bootstrapbase/templates/report_insights/insight_details.mustache +++ /dev/null @@ -1,108 +0,0 @@ -{{! - 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 report_insights/insight_details - - Actions panel at the bottom of the assignment grading UI. - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * none - - Example context (json): - { - "insightname": "Best insight ever", - "sampleimage": "Link", - "sampledescription": "Sample description", - "style": "success", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "predictiondisplayvalue": "This dev will understand it", - "calculations": [ - { - "style": "success", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "name": "Indicator 1", - "displayvalue": "yes" - }, { - "style": "info", - "name": "Indicator 2", - "displayvalue": "20%" - }, { - "style": "", - "name": "Indicator 2", - "displayvalue": "20%" - }, { - "style": "warning", - "name": "Indicator 2", - "displayvalue": "20%" - }, { - "style": "danger", - "name": "Indicator 2", - "displayvalue": "20%" - } - ] - } -}} - -

{{#str}}insightprediction, report_insights, {{insightname}} {{/str}}

- - - - - - - - - - - {{> report_insights/insight}} - -
{{#str}}insight, report_insights{{/str}}
{{#str}}name{{/str}}{{#str}}prediction, report_insights{{/str}}{{#str}}actions{{/str}}
- - - - - - - - - - - {{#calculations}} - - - - - {{/calculations}} - -
{{#str}}predictiondetails, report_insights{{/str}}
{{#str}}indicator, report_insights{{/str}}{{#str}}calculatedvalue, report_insights{{/str}}
{{name}}{{#outcomeicon}}{{> core/pix_icon}}{{/outcomeicon}} {{displayvalue}}
-{{#nocalculations}} - {{> core/notification_info}} -{{/nocalculations}} diff --git a/theme/bootstrapbase/templates/report_insights/insights_list.mustache b/theme/bootstrapbase/templates/report_insights/insights_list.mustache deleted file mode 100644 index ecb26899d65..00000000000 --- a/theme/bootstrapbase/templates/report_insights/insights_list.mustache +++ /dev/null @@ -1,91 +0,0 @@ -{{! - 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 report_insights/insights_list - - Template for the insights list. - - Classes required for JS: - * none - - Data attributes required for JS: - * none - - Context variables required for this template: - * none - - Example context (json): - { - "insightname": "Best insight ever", - "insights": [ - { - "sampleimage": "Link", - "sampledescription": "Sample description", - "style": "success", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "predictiondisplayvalue": "This dev will understand it" - }, { - "sampleimage": "Any renderable", - "sampledescription": "Another sample description", - "style": "danger", - "outcomeicon": { - "attributes": [ - {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } - ] - }, - "predictiondisplayvalue": "This dev will not understand it" - } - ], - "noinsights": false - } -}} - -{{#modelselector}} -
- {{> core/single_select}} -
-{{/modelselector}} - -

{{{insightname}}}

-{{^noinsights}} -{{{ pagingbar }}} - - - - - - - - - - - {{#insights}} - {{> report_insights/insight}} - {{/insights}} - -
{{#str}}insights, report_insights{{/str}}
{{#str}}name{{/str}}{{#str}}prediction, report_insights{{/str}}{{#str}}actions{{/str}}
-{{{ pagingbar }}} -{{/noinsights}} -{{#noinsights}} -
- {{> core/notification_info}} -
-{{/noinsights}} diff --git a/version.php b/version.php index b9152fc754c..05548c3e1c8 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2017101000.01; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2017101000.02; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes.