commit 469e716b2e97dccf6b3e47111ff664846fea34ec Author: Daniel Neis Araujo Date: Mon Dec 9 14:54:48 2019 -0300 Insights de vĂ¡rios cursos da mesma categoria diff --git a/report/insights/classes/output/multi_insights_list.php b/report/insights/classes/output/multi_insights_list.php new file mode 100644 index 00000000000..51859765ee7 --- /dev/null +++ b/report/insights/classes/output/multi_insights_list.php @@ -0,0 +1,260 @@ +. + +/** + * Multi Insights list page. + * + * @package report_insights + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @copyright 2019 Daniel Neis Araujo + * @copyright 2019 Thiago Livramento + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace report_insights\output; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Shows report_insights multi insights list. + * + * @package report_insights + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @copyright 2019 Daniel Neis Araujo + * @copyright 2019 Thiago Livramento + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class multi_insights_list implements \renderable, \templatable { + + /** + * @var \core_analytics\model + */ + protected $model; + + /** + * @var \context + */ + protected $context; + + /** + * @var \core_analytics\model[] + */ + protected $othermodels; + + /** + * @var int + */ + protected $page; + + /** + * @var int + */ + protected $perpage; + + /** + * Constructor + * + * @param \core_analytics\model $model + * @param \context[] $context + * @param \core_analytics\model[] $othermodels + * @param int $page + * @param int $perpage The max number of results to fetch + * @return void + */ + public function __construct(\core_analytics\model $model, array $contexts, $othermodels, $page = 0, $perpage = 100) { + $this->model = $model; + $this->contexts = $contexts; + $this->othermodels = $othermodels; + $this->page = $page; + $this->perpage = $perpage; + } + + /** + * Exports the data. + * + * @param \renderer_base $output + * @return \stdClass + */ + public function export_for_template(\renderer_base $output) { + global $PAGE; + + $target = $this->model->get_target(); + $allusers = []; + $return['contexts'] = []; + foreach ($this->contexts as $context) { + $allusers += get_enrolled_users($context); + $return['contexts'][] = $context->get_context_name(); + } + foreach ($allusers as $key => $user) { + $allusers[$key]->fullname = fullname($user); + } + + foreach ($this->contexts as $context) { + + $this->context = $context; + + $data = new \stdClass(); + $data->modelid = $this->model->get_id(); + $data->contextid = $this->context->id; + + $targetname = $target->get_name(); + $data->insightname = format_string($targetname); + + $targetinfostr = $targetname->get_identifier() . 'info'; + if (get_string_manager()->string_exists($targetinfostr, $targetname->get_component())) { + $data->insightdescription = get_string($targetinfostr, $targetname->get_component()); + } + + $data->showpredictionheading = true; + if (!$target->is_linear()) { + $nclasses = count($target::get_classes()); + $nignoredclasses = count($target->ignored_predicted_classes()); + if ($nclasses - $nignoredclasses <= 1) { + // Hide the prediction heading if there is only 1 class displayed. Otherwise it is redundant with the insight name. + $data->showpredictionheading = false; + } + } + + $total = 0; + + if ($this->model->uses_insights()) { + + //$target->add_bulk_actions_js(); + + $predictionsdata = $this->model->get_predictions($this->context, true, $this->page, $this->perpage); + + if (!$this->model->is_static()) { + $notification = new \core\output\notification(get_string('justpredictions', 'report_insights')); + $data->nostaticmodelnotification = $notification->export_for_template($output); + } + + $data->predictions = array(); + $predictionvalues = array(); + $insights = array(); + if ($predictionsdata) { + list($total, $predictions) = $predictionsdata; + + /* + if ($predictions) { + // No bulk actions if no predictions. + $data->bulkactions = actions_exporter::add_bulk_actions($target, $output, $predictions, $this->context); + } + */ + + $data->multiplepredictions = count($predictions) > 1 ? true : false; + + foreach ($predictions as $prediction) { + $predictedvalue = $prediction->get_prediction_data()->prediction; + $userid = $prediction->get_sample_data()['user']->id; + + // Only need to fill this data once. + if (!isset($predictionvalues[$predictedvalue])) { + $preddata = array(); + $preddata['predictiondisplayvalue'] = $target->get_display_value($predictedvalue); + list($preddata['style'], $preddata['outcomeicon']) = + insight::get_calculation_display($target, floatval($predictedvalue), $output); + $predictionvalues[$predictedvalue] = $preddata; + } + + $insightrenderable = new \report_insights\output\insight($prediction, $this->model, true, $this->context); + $insightexported = $insightrenderable->export_for_template($output); + $insights[$predictedvalue][] = $insightexported; + if (!isset($allusers[$userid]->predictions)) { + $allusers[$userid]->predictions = []; + } + $allusers[$userid]->predictions[$data->contextid]['insights'][] = $insightexported; + } + + // Order predicted values. + if ($target->is_linear()) { + // During regression what we will be interested on most of the time is in low values so let's show them first. + ksort($predictionvalues); + } else { + // During classification targets flag "not that important" samples as 0 so let's show them at the end. + krsort($predictionvalues); + } + + // 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])) { + + /* + $toggleall = new \core\output\checkbox_toggleall('insight-bulk-action-' . $key, true, [ + 'id' => 'id-toggle-all-' . $key, + 'name' => 'toggle-all-' . $key, + 'label' => get_string('selectall'), + 'labelclasses' => 'sr-only', + 'checked' => false + ]); + $prediction['checkboxtoggleall'] = $output->render($toggleall); + + $prediction['predictedvalue'] = $key; + */ + $prediction['insights'] = $insights[$key]; + } + + $data->predictions[] = $prediction; + } + } + + if (empty($insights) && $this->page == 0) { + if ($this->model->any_prediction_obtained()) { + $data->noinsights = get_string('noinsights', 'analytics'); + } else { + $data->noinsights = get_string('nopredictionsyet', 'analytics'); + } + } + } else { + $data->noinsights = get_string('noinsights', 'analytics'); + } + + if (!empty($data->noinsights)) { + $notification = new \core\output\notification($data->noinsights); + $data->noinsights = $notification->export_for_template($output); + } + + if ($this->othermodels) { + + $options = array(); + foreach ($this->othermodels as $model) { + $options[$model->get_id()] = $model->get_target()->get_name(); + } + + // New moodle_url instance returned by magic_get_url. + $url = $PAGE->url; + $url->remove_params('modelid'); + $modelselector = new \single_select($url, 'modelid', $options, '', + array('' => get_string('selectotherinsights', 'report_insights'))); + $data->modelselector = $modelselector->export_for_template($output); + } + + $data->pagingbar = $output->render(new \paging_bar($total, $this->page, $this->perpage, $PAGE->url)); + + $return['data'][] = $data; + foreach ($allusers as $key => $user) { + if (!isset($user->predictions[$data->contextid])) { + $allusers[$user->id]->predictions[$data->contextid] = ['insights' => []]; + } + } + } + $return['allusers'] = array_values($allusers); + foreach ($return['allusers'] as $key => $user) { + $return['allusers'][$key]->predictions = array_values($return['allusers'][$key]->predictions); + } + + return $return; + } +} diff --git a/report/insights/classes/output/renderer.php b/report/insights/classes/output/renderer.php index b8fefdcb76d..2879cae72cb 100644 --- a/report/insights/classes/output/renderer.php +++ b/report/insights/classes/output/renderer.php @@ -50,6 +50,17 @@ class renderer extends plugin_renderer_base { return parent::render_from_template('report_insights/insights_list', $data); } + /** + * Renders the list of insights + * + * @param renderable $renderable + * @return string HTML + */ + protected function render_multi_insights_list(renderable $renderable) { + $data = $renderable->export_for_template($this); + return parent::render_from_template('report_insights/multi_insights_list', $data); + } + /** * Renders an insight * diff --git a/report/insights/insights.php b/report/insights/insights.php index c45aaf9e79b..6642bcfe8c2 100644 --- a/report/insights/insights.php +++ b/report/insights/insights.php @@ -127,6 +127,9 @@ echo $OUTPUT->header(); $renderable = new \report_insights\output\insights_list($model, $context, $othermodels, $page, $perpage); echo $renderer->render($renderable); +$multi = new moodle_url('/report/insights/multiinsights.php', ['contextid' => $contextid, 'modelid' => $modelid]); +echo html_writer::link($multi, 'Ver todos os cursos nesta categoria'); + $eventdata = array ( 'context' => $context, 'other' => array('modelid' => $model->get_id()) diff --git a/report/insights/multiinsights.php b/report/insights/multiinsights.php new file mode 100644 index 00000000000..f933ae48e00 --- /dev/null +++ b/report/insights/multiinsights.php @@ -0,0 +1,174 @@ +. + +/** + * View model insights. + * + * @package report_insights + * @copyright 2017 David Monllao {@link http://www.davidmonllao.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(__DIR__ . '/../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +$contextid = required_param('contextid', PARAM_INT); +$modelid = optional_param('modelid', false, PARAM_INT); +$page = optional_param('page', 0, PARAM_INT); +$perpage = optional_param('perpage', 100, PARAM_INT); + +if ($perpage > 1000) { + $perpage = 1000; +} + +list($context, $course, $cm) = get_context_info_array($contextid); +require_login($course, false, $cm); +if ($context->contextlevel < CONTEXT_COURSE) { + // Only for higher levels than course. + $PAGE->set_context($context); +} + +#if (!\core_analytics\manager::is_analytics_enabled()) { +# $renderer = $PAGE->get_renderer('report_insights'); +# echo $renderer->render_analytics_disabled(); +# exit(0); +#} + +\core_analytics\manager::check_can_list_insights($context); + +// Get all models that are enabled, trained and have predictions at this context. +$othermodels = \core_analytics\manager::get_all_models(true, true, $context); +array_filter($othermodels, function($model) use ($context) { + + // Discard insights that are not linked unless you are a manager. + /* + if (!$model->get_target()->link_insights_report()) { + try { + \core_analytics\manager::check_can_manage_models(); + } catch (\required_capability_exception $e) { + return false; + } + } + */ + return true; +}); + +if (!$modelid && count($othermodels)) { + // Autoselect the only available model. + $model = reset($othermodels); + $modelid = $model->get_id(); +} +if ($modelid) { + unset($othermodels[$modelid]); +} + +// The URL in navigation only contains the contextid. +$params = array('contextid' => $contextid); +$navurl = new \moodle_url('/report/insights/insights.php', $params); + +// This is the real page url, we need it to include the modelid so pagination and +// other stuff works as expected. +$url = clone $navurl; +if ($modelid) { + $url->param('modelid', $modelid); +} + +$PAGE->set_url($url); +$PAGE->set_pagelayout('report'); + +if ($context->contextlevel === CONTEXT_SYSTEM) { + admin_externalpage_setup('reportinsights', '', $url->params(), $url->out(false), array('pagelayout' => 'report')); +} else if ($context->contextlevel === CONTEXT_USER) { + $user = \core_user::get_user($context->instanceid, '*', MUST_EXIST); + $PAGE->navigation->extend_for_user($user); + $PAGE->add_report_nodes($user->id, array( + 'name' => get_string('insights', 'report_insights'), + 'url' => $url + )); +} +$PAGE->navigation->override_active_url($navurl); + +$renderer = $PAGE->get_renderer('report_insights'); + +// No models with insights available at this context level. +if (!$modelid) { + echo $renderer->render_no_insights($context); + exit(0); +} + +$model = new \core_analytics\model($modelid); + +/* +if (!$model->get_target()->link_insights_report()) { + + // Only manager access if this target does not link the insights report. + \core_analytics\manager::check_can_manage_models(); +} + */ +$insightinfo = new stdClass(); +$insightinfo->contextname = $context->get_context_name(); +$insightinfo->insightname = $model->get_target()->get_name(); + +if (!$model->is_enabled()) { + echo $renderer->render_model_disabled($insightinfo); + exit(0); +} + +if (!$model->uses_insights()) { + echo $renderer->render_no_insights_model($context); + exit(0); +} + +if ($context->id == SYSCONTEXTID) { + $PAGE->set_heading(get_site()->shortname); +} else { + $PAGE->set_heading($insightinfo->contextname); +} +$PAGE->set_title($insightinfo->insightname); + +// Some models generate one single prediction per context. We can directly show the prediction details in this case. +if ($model->get_analyser()::one_sample_per_analysable()) { + + // Param $perpage to 2 so we can detect if this model's analyser is using one_sample_per_analysable incorrectly. + $predictionsdata = $model->get_predictions($context, true, 0, 2); + if ($predictionsdata) { + list($total, $predictions) = $predictionsdata; + if ($total > 1) { + throw new \coding_exception('This model\'s analyser processed more than one sample for a single analysable element.' . + 'Therefore, the analyser\'s one_sample_per_analysable() method should return false.'); + } + $prediction = reset($predictions); + $redirecturl = new \moodle_url('/report/insights/prediction.php', ['id' => $prediction->get_prediction_data()->id]); + redirect($redirecturl); + } +} +echo $OUTPUT->header(); + +$category = core_course_category::get($course->category); +$contexts = []; +foreach ($category->get_courses() as $c) { + $contexts[] = \context_course::instance($c->id); +} +$renderable = new \report_insights\output\multi_insights_list($model, $contexts, $othermodels, $page, $perpage); +echo $renderer->render($renderable); + +$eventdata = array ( + 'context' => $context, + 'other' => array('modelid' => $model->get_id()) +); +\core\event\insights_viewed::create($eventdata)->trigger(); + +echo $OUTPUT->footer(); diff --git a/report/insights/templates/multi_insights_list.mustache b/report/insights/templates/multi_insights_list.mustache new file mode 100644 index 00000000000..5cf31ece5c4 --- /dev/null +++ b/report/insights/templates/multi_insights_list.mustache @@ -0,0 +1,153 @@ +{{! + 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): + { + "contextid": 123, + "modelid": 321, + "insightname": "Best insight ever", + "nostaticmodelnotification": { + "message": "This is just a prediction." + }, + "showpredictionheading": "true", + "predictions": [ + { + "predictiondisplayvalue": "This dev will understand it", + "style": "success", + "outcomeicon": { + "attributes": [ + {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } + ] + }, + "multiplepredictions": "true", + "insights": [ + { + "sampleimage": "Link", + "sampledescription": "Sample description", + "actions": [ + { + "classes": "", + "primary": { + "items": [{"rawhtml": "

View details

"}] + }, + "secondary": { + "items": [{"rawhtml": "

Not useful

"}] + } + } + ] + }, { + "sampleimage": "Link", + "sampledescription": "Sample description", + "actions": [ + { + "classes": "", + "primary": { + "items": [{"rawhtml": "

View details

"}] + }, + "secondary": { + "items": [{"rawhtml": "

Not useful

"}] + } + } + ] + } + ] + }, { + "predictiondisplayvalue": "This dev will not understand it", + "style": "danger", + "outcomeicon": { + "attributes": [ + {"name": "src", "value": "https://moodle.org/logo/moodle-logo.svg" } + ] + }, + "insights": [ + { + "sampleimage": "Any renderable", + "sampledescription": "Another sample description" + } + ] + } + ], + "noinsights": false + } +}} + +{{#modelselector}} +
+ {{> core/single_select}} +
+{{/modelselector}} + +

{{insightname}}

+ +{{{insightdescription}}} + +{{#showpredictionheading}} + + {{#str}}prediction, report_insights{{/str}}: + + {{#outcomeicon}} + {{> core/pix_icon}} + {{/outcomeicon}} + {{predictiondisplayvalue}} + + +{{/showpredictionheading}} + +
+ + + + + {{#contexts}} + + {{/contexts}} + + + + {{#allusers}} + + + {{#predictions}} + {{#insights}} + + {{/insights}} + {{^insights}} + + {{/insights}} + {{/predictions}} + + {{/allusers}} + +
{{#str}} fullname {{/str}}{{{.}}}
{{{fullname}}} + {{#actions}} + {{> core/action_menu}} + {{/actions}} + --
+