diff --git a/admin/tool/analytics/classes/output/effectiveness_report.php b/admin/tool/analytics/classes/output/effectiveness_report.php
new file mode 100644
index 00000000000..906ecb7d27a
--- /dev/null
+++ b/admin/tool/analytics/classes/output/effectiveness_report.php
@@ -0,0 +1,179 @@
+.
+
+/**
+ * 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];
+ }
+}
diff --git a/admin/tool/analytics/classes/output/helper.php b/admin/tool/analytics/classes/output/helper.php
index 1db998157f6..7d286253f41 100644
--- a/admin/tool/analytics/classes/output/helper.php
+++ b/admin/tool/analytics/classes/output/helper.php
@@ -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);
+ }
}
diff --git a/admin/tool/analytics/classes/output/models_list.php b/admin/tool/analytics/classes/output/models_list.php
index fbbc2275da2..5002af020e8 100644
--- a/admin/tool/analytics/classes/output/models_list.php
+++ b/admin/tool/analytics/classes/output/models_list.php
@@ -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';
diff --git a/admin/tool/analytics/classes/output/renderer.php b/admin/tool/analytics/classes/output/renderer.php
index 743844ea0b6..60bc2a22503 100644
--- a/admin/tool/analytics/classes/output/renderer.php
+++ b/admin/tool/analytics/classes/output/renderer.php
@@ -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.
*
diff --git a/admin/tool/analytics/lang/en/tool_analytics.php b/admin/tool/analytics/lang/en/tool_analytics.php
index 0979652d5a8..b1fa75d924f 100644
--- a/admin/tool/analytics/lang/en/tool_analytics.php
+++ b/admin/tool/analytics/lang/en/tool_analytics.php
@@ -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';
\ No newline at end of file
+$string['getpredictions'] = 'Get predictions';
diff --git a/admin/tool/analytics/model.php b/admin/tool/analytics/model.php
index ae2c8dc84a6..4174ce11d7d 100644
--- a/admin/tool/analytics/model.php
+++ b/admin/tool/analytics/model.php
@@ -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();
diff --git a/admin/tool/analytics/templates/effectiveness_report.mustache b/admin/tool/analytics/templates/effectiveness_report.mustache
new file mode 100644
index 00000000000..c603d609de4
--- /dev/null
+++ b/admin/tool/analytics/templates/effectiveness_report.mustache
@@ -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