MDL-65562 tool_analytics: Effectiveness report base

This commit is contained in:
David Monllaó 2019-05-14 12:04:24 +02:00
parent fac49f8f72
commit 4de032eba3
8 changed files with 340 additions and 23 deletions

View File

@ -0,0 +1,179 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
/**
* Effectiveness report renderable.
*
* @package tool_analytics
* @copyright 2019 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;
/**
* Effectiveness report renderable.
*
* @package tool_analytics
* @copyright 2019 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class effectiveness_report implements \renderable, \templatable {
/**
* @var \core_analytics\model
*/
private $model = null;
/**
* @var \context
*/
private $context = null;
/**
* Inits the effectiveness report renderable.
*
* @param \core_analytics\model $model
* @param int|null $contextid
* @return null
*/
public function __construct(\core_analytics\model $model, ?int $contextid = null) {
$this->model = $model;
if ($contextid) {
$this->context = \context::instance_by_id($contextid);
}
}
/**
* Export the data.
*
* @param \renderer_base $output
* @return \stdClass
*/
public function export_for_template(\renderer_base $output): \stdClass {
// Prepare the context object.
$data = new \stdClass();
$data->modelname = $this->model->get_name();
$data->charts = [];
$predictionactionrecords = $this->model->get_prediction_actions($this->context);
// Context selector.
$predictioncontexts = $this->model->get_predictions_contexts(false);
if ($predictioncontexts && count($predictioncontexts) > 1) {
$url = new \moodle_url('/admin/tool/analytics/model.php', ['id' => $this->model->get_id(),
'action' => 'effectivenessreport']);
if ($this->context) {
$selected = $this->context->id;
} else {
// This is the 'all' option.
$selected = 0;
}
$data->contextselect = \tool_analytics\output\helper::prediction_context_selector($predictioncontexts,
$url, $output, $selected, true, false);
}
if ($predictionactionrecords->valid()) {
foreach ($predictionactionrecords as $record) {
// Using this unusual execution flow to init the chart data because $predictionactionrecords
// is a \moodle_recordset.
if (empty($actionlabels)) {
list($actionlabels, $actionvalues) = $this->init_action_labels($record);
}
// One value for each action.
$actionvalues['separated'][$record->actionname]++;
// Data grouped in three boxes.
if ($record->actionname == 'notuseful') {
$actionvalues['grouped']['negative']++;
} else if ($record->actionname == 'predictiondetails') {
$actionvalues['grouped']['neutral']++;
} else {
$actionvalues['grouped']['positive']++;
}
}
$predictionactionrecords->close();
// Actions doughtnut.
$chart = new \core\chart_pie();
$chart->set_doughnut(true);
$chart->set_title(get_string('actionsexecutedbyusers', 'tool_analytics'));
$series = new \core\chart_series(get_string('actions', 'tool_analytics'),
array_values($actionvalues['separated']));
$chart->add_series($series);
$chart->set_labels(array_values($actionlabels['separated']));
$data->separatedchart = $output->render($chart);
// Positive/negative/neutral bar chart.
$chart = new \core\chart_bar();
$chart->set_title(get_string('actionexecutedgroupedusefulness', 'tool_analytics'));
$series = new \core\chart_series(get_string('actions', 'tool_analytics'),
array_values($actionvalues['grouped']));
$chart->add_series($series);
$chart->set_labels(array_values($actionlabels['grouped']));
$data->groupedchart = $output->render($chart);
} else {
$predictionactionrecords->close();
$data->noactions = [
'message' => get_string('noactionsfound', 'tool_analytics'),
'announce' => true,
];
}
return $data;
}
/**
* Initialises the action labels and values in this model.
*
* @param \stdClass $predictionactionrecord
* @return array Two-dimensional array with the labels and values initialised to zero.
*/
private function init_action_labels(\stdClass $predictionactionrecord): array {
$predictioncontext = \context::instance_by_id($predictionactionrecord->contextid);
// Just 1 result, we just want to retrieve the prediction action names.
list ($unused, $predictions) = $this->model->get_predictions($predictioncontext, false, 0, 1);
// We pass 'true' for $isinsightuser so all the prediction actions available for this target are returning.
$predictionactions = $this->model->get_target()->prediction_actions(reset($predictions), true, true);
$actionlabels = [];
$actionvalues = ['separated' => [], 'grouped' => []];
foreach ($predictionactions as $action) {
$actionlabels['separated'][$action->get_action_name()] = $action->get_text();
$actionvalues['separated'][$action->get_action_name()] = 0;
}
$actionlabels['grouped']['positive'] = get_string('useful', 'analytics');
$actionlabels['grouped']['neutral'] = get_string('neutral', 'analytics');
$actionlabels['grouped']['negative'] = get_string('notuseful', 'analytics');
$actionvalues['grouped']['positive'] = 0;
$actionvalues['grouped']['neutral'] = 0;
$actionvalues['grouped']['negative'] = 0;
return [$actionlabels, $actionvalues];
}
}

View File

@ -105,4 +105,50 @@ class helper {
$PAGE->reset_theme_and_output();
$PAGE->set_context(\context_system::instance());
}
/**
* Convert a list of contexts to an associative array where the value is the context name.
*
* @param array $contexts
* @param \moodle_url $url
* @param \renderer_base $output
* @param int|null $selected
* @param bool $includeall
* @param bool $shortentext
* @return \stdClass
*/
public static function prediction_context_selector(array $contexts, \moodle_url $url, \renderer_base $output,
?int $selected = null, ?bool $includeall = false, ?bool $shortentext = true): \stdClass {
foreach ($contexts as $contextid => $unused) {
// We prepare this to be used as single_select template options.
$context = \context::instance_by_id($contextid);
// Special name for system level predictions as showing "System is not visually nice".
if ($contextid == SYSCONTEXTID) {
$contextname = get_string('allpredictions', 'tool_analytics');
} else {
if ($shortentext) {
$contextname = shorten_text($context->get_context_name(false, true), 40);
} else {
$contextname = $context->get_context_name(false, true);
}
}
$contexts[$contextid] = $contextname;
}
if ($includeall) {
$contexts[0] = get_string('all');
$nothing = '';
} else {
$nothing = array('' => 'choosedots');
}
\core_collator::asort($contexts);
if (!$selected) {
$selected = '';
}
$singleselect = new \single_select($url, 'contextid', $contexts, $selected, $nothing);
return $singleselect->export_for_template($output);
}
}

View File

@ -166,36 +166,20 @@ class models_list implements \renderable, \templatable {
// Has this model generated predictions?.
$predictioncontexts = $model->get_predictions_contexts();
$anypredictionobtained = $model->any_prediction_obtained();
// Model predictions list.
if (!$model->is_enabled()) {
$modeldata->noinsights = get_string('disabledmodel', 'analytics');
} else if ($model->uses_insights()) {
if ($predictioncontexts) {
foreach ($predictioncontexts as $contextid => $unused) {
// We prepare this to be used as single_select template options.
$context = \context::instance_by_id($contextid);
// Special name for system level predictions as showing "System is not visually nice".
if ($contextid == SYSCONTEXTID) {
$contextname = get_string('allpredictions', 'tool_analytics');
} else {
$contextname = shorten_text($context->get_context_name(false, true), 40);
}
$predictioncontexts[$contextid] = $contextname;
}
\core_collator::asort($predictioncontexts);
if (!empty($predictioncontexts)) {
$url = new \moodle_url('/report/insights/insights.php', array('modelid' => $model->get_id()));
$singleselect = new \single_select($url, 'contextid', $predictioncontexts);
$modeldata->insights = $singleselect->export_for_template($output);
}
$url = new \moodle_url('/report/insights/insights.php', array('modelid' => $model->get_id()));
$modeldata->insights = \tool_analytics\output\helper::prediction_context_selector($predictioncontexts,
$url, $output);
}
if (empty($modeldata->insights)) {
if ($model->any_prediction_obtained()) {
if ($anypredictionobtained) {
$modeldata->noinsights = get_string('noinsights', 'analytics');
} else {
$modeldata->noinsights = get_string('nopredictionsyet', 'analytics');
@ -304,6 +288,15 @@ class models_list implements \renderable, \templatable {
}
}
// Effectivity report.
if (!empty($anypredictionobtained) && $model->uses_insights()) {
$urlparams['action'] = 'effectivenessreport';
$url = new \moodle_url('/admin/tool/analytics/model.php', $urlparams);
$pix = new \pix_icon('i/report', get_string('effectivenessreport', 'tool_analytics'));
$icon = new \action_menu_link_secondary($url, $pix, get_string('effectivenessreport', 'tool_analytics'));
$actionsmenu->add($icon);
}
// Invalid analysables.
$analyser = $model->get_analyser(['notimesplitting' => true]);
if (!$analyser instanceof \core_analytics\local\analyser\sitewide) {
@ -315,7 +308,7 @@ class models_list implements \renderable, \templatable {
}
// Clear model.
if (!empty($predictioncontexts) || $model->is_trained()) {
if (!empty($anypredictionobtained) || $model->is_trained()) {
$actionid = 'clear-' . $model->get_id();
$PAGE->requires->js_call_amd('tool_analytics/model', 'confirmAction', [$actionid, 'clear']);
$urlparams['action'] = 'clear';

View File

@ -208,6 +208,17 @@ class renderer extends plugin_renderer_base {
return $output;
}
/**
* Defer to template.
*
* @param \tool_analytics\output\effectiveness_report $effectivenessreport
* @return string HTML
*/
protected function render_effectiveness_report(\tool_analytics\output\effectiveness_report $effectivenessreport): string {
$data = $effectivenessreport->export_for_template($this);
return parent::render_from_template('tool_analytics/effectiveness_report', $data);
}
/**
* Defer to template.
*

View File

@ -23,6 +23,9 @@
*/
$string['accuracy'] = 'Accuracy';
$string['actions'] = 'Actions';
$string['actionsexecutedbyusers'] = 'Actions executed by users';
$string['actionexecutedgroupedusefulness'] = 'Grouped actions';
$string['allpredictions'] = 'All predictions';
$string['alltimesplittingmethods'] = 'All analysis intervals';
$string['analysingsitedata'] = 'Analysing the site';
@ -48,6 +51,8 @@ $string['deletemodelconfirmation'] = 'Are you sure you want to delete "{$a}"? Th
$string['disabled'] = 'Disabled';
$string['editmodel'] = 'Edit "{$a}" model';
$string['edittrainedwarning'] = 'This model has already been trained. Note that changing its indicators or its analysis interval will delete its previous predictions and start generating new predictions.';
$string['effectivenessreport'] = 'Effectiveness report';
$string['effectivenessreportfor'] = 'Model "{$a}" effectiveness';
$string['enabled'] = 'Enabled';
$string['errorcantenablenotimesplitting'] = 'You need to select an analysis interval before enabling the model';
$string['errornoenabledandtrainedmodels'] = 'There are no enabled and trained models to predict.';
@ -105,6 +110,7 @@ $string['modelresults'] = '{$a} results';
$string['modeltimesplitting'] = 'Analysis interval';
$string['newmodel'] = 'New model';
$string['nextpage'] = 'Next page';
$string['noactionsfound'] = 'Users have not executed any actions on the generated insights.';
$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.';
@ -143,4 +149,4 @@ $string['weeksenddatedefault'] = 'End date automatically calculated from the cou
$string['privacy:metadata'] = 'The Analytic models plugin does not store any personal data.';
// Deprecated since Moodle 3.8.
$string['getpredictions'] = 'Get predictions';
$string['getpredictions'] = 'Get predictions';

View File

@ -69,6 +69,9 @@ switch ($action) {
case 'clear':
$title = get_string('clearpredictions', 'tool_analytics');
break;
case 'effectivenessreport':
$title = get_string('effectivenessreport', 'tool_analytics');
break;
case 'invalidanalysables':
$title = get_string('invalidanalysables', 'tool_analytics');
break;
@ -273,6 +276,18 @@ switch ($action) {
redirect($returnurl);
break;
case 'effectivenessreport':
$contextid = optional_param('contextid', null, PARAM_INT);
echo $OUTPUT->header();
$renderable = new \tool_analytics\output\effectiveness_report($model, $contextid);
$renderer = $PAGE->get_renderer('tool_analytics');
echo $renderer->render($renderable);
break;
case 'invalidanalysables':
echo $OUTPUT->header();

View File

@ -0,0 +1,65 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template tool_analytics/effectiveness_report
Template for the effectiveness report.
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",
"noactions": {
"message": "Users have not executed any actions on the generated insights.",
"announce": "true"
}
}
}}
<div class="box">
<h3>{{#str}}effectivenessreportfor, tool_analytics, {{modelname}}{{/str}}</h3>
{{#contextselect}}
<div class="mt-3">
{{> core/single_select }}
</div>
{{/contextselect}}
{{#noactions}}
<div class="mt-3 mb-1">
{{> core/notification_info}}
</div>
{{/noactions}}
{{^noanalysables}}
<div class="row">
<div class="col-xl-6">
{{{separatedchart}}}
</div>
<div class="col-xl-6">
{{{groupedchart}}}
</div>
</div>
{{/noanalysables}}
</div>

View File

@ -91,6 +91,7 @@ $string['modeloutputdir'] = 'Models output directory';
$string['modeloutputdirinfo'] = 'Directory where prediction processors store all evaluation info. Useful for debugging and research.';
$string['modeltimelimit'] = 'Analysis time limit per model';
$string['modeltimelimitinfo'] = 'This setting limits the time each model spends analysing the site contents.';
$string['neutral'] = 'Neutral';
$string['noevaluationbasedassumptions'] = 'Models based on assumptions cannot be evaluated.';
$string['nodata'] = 'No data to analyse';
$string['noinsightsmodel'] = 'This model does not generate insights';
@ -143,6 +144,7 @@ $string['typeinstitution'] = 'Type of institution';
$string['typeinstitutionacademic'] = 'Academic';
$string['typeinstitutiontraining'] = 'Corporate training';
$string['typeinstitutionngo'] = 'Non-governmental organization (NGO)';
$string['useful'] = 'Useful';
$string['viewdetails'] = 'View details';
$string['viewinsight'] = 'View insight';
$string['viewinsightdetails'] = 'View insight details';