Merge branch 'MDL-58835_master' of git://github.com/dmonllao/moodle

This commit is contained in:
Dan Poltawski 2017-09-12 09:44:56 +01:00
commit 8e085aacc0
20 changed files with 724 additions and 48 deletions

View File

@ -112,18 +112,42 @@ abstract class base extends \core_analytics\calculable {
* @return \core_analytics\prediction_action[]
*/
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) {
global $PAGE;
$predictionid = $prediction->get_prediction_data()->id;
$PAGE->requires->js_call_amd('report_insights/actions', 'init', array($predictionid));
$actions = array();
if ($includedetailsaction) {
$predictionurl = new \moodle_url('/report/insights/prediction.php',
array('id' => $prediction->get_prediction_data()->id));
array('id' => $predictionid));
$actions['predictiondetails'] = new \core_analytics\prediction_action('predictiondetails', $prediction,
$actions[] = new \core_analytics\prediction_action(\core_analytics\prediction::ACTION_PREDICTION_DETAILS, $prediction,
$predictionurl, new \pix_icon('t/preview', get_string('viewprediction', 'analytics')),
get_string('viewprediction', 'analytics'));
}
// Flag as fixed / solved.
$fixedattrs = array(
'data-prediction-id' => $predictionid,
'data-prediction-methodname' => 'report_insights_set_fixed_prediction'
);
$actions[] = new \core_analytics\prediction_action(\core_analytics\prediction::ACTION_FIXED,
$prediction, new \moodle_url(''), new \pix_icon('t/check', get_string('fixedack', 'analytics')),
get_string('fixedack', 'analytics'), false, $fixedattrs);
// Flag as not useful.
$notusefulattrs = array(
'data-prediction-id' => $predictionid,
'data-prediction-methodname' => 'report_insights_set_notuseful_prediction'
);
$actions[] = new \core_analytics\prediction_action(\core_analytics\prediction::ACTION_NOT_USEFUL,
$prediction, new \moodle_url(''), new \pix_icon('t/delete', get_string('notuseful', 'analytics')),
get_string('notuseful', 'analytics'), false, $notusefulattrs);
return $actions;
}

View File

@ -1041,15 +1041,29 @@ class model {
/**
* Get the contexts with predictions.
*
* @param bool $skiphidden Skip hidden predictions
* @return \stdClass[]
*/
public function get_predictions_contexts() {
global $DB;
public function get_predictions_contexts($skiphidden = true) {
global $DB, $USER;
$sql = "SELECT DISTINCT ap.contextid FROM {analytics_predictions} ap
JOIN {context} ctx ON ctx.id = ap.contextid
WHERE ap.modelid = ?";
return $DB->get_records_sql($sql, array($this->model->id));
WHERE ap.modelid = :modelid";
$params = array('modelid' => $this->model->id);
if ($skiphidden) {
$sql .= " AND NOT EXISTS (
SELECT 1
FROM {analytics_prediction_actions} apa
WHERE apa.predictionid = ap.id AND apa.userid = :userid AND (apa.actionname = :fixed OR apa.actionname = :notuseful)
)";
$params['userid'] = $USER->id;
$params['fixed'] = \core_analytics\prediction::ACTION_FIXED;
$params['notuseful'] = \core_analytics\prediction::ACTION_NOT_USEFUL;
}
return $DB->get_records_sql($sql, $params);
}
/**
@ -1096,12 +1110,13 @@ class model {
* Gets the predictions for this context.
*
* @param \context $context
* @param bool $skiphidden Skip hidden predictions
* @param int $page The page of results to fetch. False for all results.
* @param int $perpage The max number of results to fetch. Ignored if $page is false.
* @return array($total, \core_analytics\prediction[])
*/
public function get_predictions(\context $context, $page = false, $perpage = 100) {
global $DB;
public function get_predictions(\context $context, $skiphidden = true, $page = false, $perpage = 100) {
global $DB, $USER;
\core_analytics\manager::check_can_list_insights($context);
@ -1111,12 +1126,27 @@ class model {
JOIN (
SELECT sampleid, max(rangeindex) AS rangeindex
FROM {analytics_predictions}
WHERE modelid = ? and contextid = ?
WHERE modelid = :modelidsubap and contextid = :contextidsubap
GROUP BY sampleid
) apsub
ON ap.sampleid = apsub.sampleid AND ap.rangeindex = apsub.rangeindex
WHERE ap.modelid = ? and ap.contextid = ?";
$params = array($this->model->id, $context->id, $this->model->id, $context->id);
WHERE ap.modelid = :modelid and ap.contextid = :contextid";
$params = array('modelid' => $this->model->id, 'contextid' => $context->id,
'modelidsubap' => $this->model->id, 'contextidsubap' => $context->id);
if ($skiphidden) {
$sql .= " AND NOT EXISTS (
SELECT 1
FROM {analytics_prediction_actions} apa
WHERE apa.predictionid = ap.id AND apa.userid = :userid AND (apa.actionname = :fixed OR apa.actionname = :notuseful)
)";
$params['userid'] = $USER->id;
$params['fixed'] = \core_analytics\prediction::ACTION_FIXED;
$params['notuseful'] = \core_analytics\prediction::ACTION_NOT_USEFUL;
}
$sql .= " ORDER BY ap.timecreated DESC";
if (!$predictions = $DB->get_records_sql($sql, $params)) {
return array();
}

View File

@ -35,6 +35,21 @@ defined('MOODLE_INTERNAL') || die();
*/
class prediction {
/**
* Prediction details (one of the default prediction actions)
*/
const ACTION_PREDICTION_DETAILS = 'predictiondetails';
/**
* Prediction not useful (one of the default prediction actions)
*/
const ACTION_NOT_USEFUL = 'notuseful';
/**
* Prediction already fixed (one of the default prediction actions)
*/
const ACTION_FIXED = 'fixed';
/**
* @var \stdClass
*/
@ -97,6 +112,51 @@ class prediction {
return $this->calculations;
}
/**
* Stores the executed action.
* Prediction instances should be retrieved using \core_analytics\manager::get_prediction,
* It is the caller responsability to check that the user can see the prediction.
*
* @param string $actionname
* @param \core_analytics\local\target\base $target
*/
public function action_executed($actionname, \core_analytics\local\target\base $target) {
global $USER, $DB;
$context = \context::instance_by_id($this->get_prediction_data()->contextid, IGNORE_MISSING);
if (!$context) {
throw new \moodle_exception('errorpredictioncontextnotavailable', 'analytics');
}
// Check that the provided action exists.
$actions = $target->prediction_actions($this, true);
foreach ($actions as $action) {
if ($action->get_action_name() === $actionname) {
$found = true;
}
}
if (empty($found)) {
throw new \moodle_exception('errorunknownaction', 'analytics');
}
$predictionid = $this->get_prediction_data()->id;
$action = new \stdClass();
$action->predictionid = $predictionid;
$action->userid = $USER->id;
$action->actionname = $actionname;
$action->timecreated = time();
$DB->insert_record('analytics_prediction_actions', $action);
$eventdata = array (
'context' => $context,
'objectid' => $predictionid,
'other' => array('actionname' => $actionname)
);
\core\event\prediction_action_started::create($eventdata)->trigger();
}
/**
* format_calculations
*

View File

@ -35,36 +35,54 @@ defined('MOODLE_INTERNAL') || die();
*/
class prediction_action {
/**
* @var string
*/
protected $actionname = null;
/**
* @var \action_menu_link
*/
protected $actionlink = null;
/**
* __construct
* Prediction action constructor.
*
* @param string $actionname
* @param string $actionname They should match a-zA-Z_0-9-, as we apply a PARAM_ALPHANUMEXT filter
* @param \core_analytics\prediction $prediction
* @param \moodle_url $actionurl
* @param \pix_icon $icon
* @param string $text
* @param bool $primary
* @param \pix_icon $icon Link icon
* @param string $text Link text
* @param bool $primary Primary button or secondary.
* @param array $attributes Link attributes
* @return void
*/
public function __construct($actionname, \core_analytics\prediction $prediction, \moodle_url $actionurl, \pix_icon $icon, $text, $primary = false) {
public function __construct($actionname, \core_analytics\prediction $prediction, \moodle_url $actionurl, \pix_icon $icon,
$text, $primary = false, $attributes = array()) {
$this->actionname = $actionname;
// We want to track how effective are our suggested actions, we pass users through a script that will log these actions.
$params = array('action' => $actionname, 'predictionid' => $prediction->get_prediction_data()->id,
$params = array('action' => $this->actionname, 'predictionid' => $prediction->get_prediction_data()->id,
'forwardurl' => $actionurl->out(false));
$url = new \moodle_url('/report/insights/action.php', $params);
if ($primary === false) {
$this->actionlink = new \action_menu_link_secondary($url, $icon, $text);
$this->actionlink = new \action_menu_link_secondary($url, $icon, $text, $attributes);
} else {
$this->actionlink = new \action_menu_link_primary($url, $icon, $text);
$this->actionlink = new \action_menu_link_primary($url, $icon, $text, $attributes);
}
}
/**
* Returns the action name.
*
* @return string
*/
public function get_action_name() {
return $this->actionname;
}
/**
* Returns the link to the action.
*

View File

@ -0,0 +1,140 @@
<?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/>.
/**
* Unit tests for prediction actions.
*
* @package core_analytics
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/fixtures/test_indicator_max.php');
require_once(__DIR__ . '/fixtures/test_target_shortname.php');
/**
* Unit tests for prediction actions.
*
* @package core_analytics
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class analytics_prediction_actions_testcase extends advanced_testcase {
/**
* Common startup tasks
*/
public function setUp() {
global $DB;
$this->setAdminUser();
$target = \core_analytics\manager::get_target('test_target_shortname');
$indicators = array('test_indicator_max');
foreach ($indicators as $key => $indicator) {
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
}
$this->model = \core_analytics\model::create($target, $indicators);
$this->modelobj = $this->model->get_model_obj();
$this->model->enable('\core\analytics\time_splitting\single_range');
$this->resetAfterTest(true);
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$this->context = \context_course::instance($course1->id);
$this->teacher1 = $this->getDataGenerator()->create_user();
$this->teacher2 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($this->teacher1->id, $course1->id, 'editingteacher');
$this->getDataGenerator()->enrol_user($this->teacher2->id, $course1->id, 'editingteacher');
// The only relevant fields are modelid, contextid and sampleid. I'm cheating and setting
// contextid as the course context so teachers can access these predictions.
$pred = new \stdClass();
$pred->modelid = $this->model->get_id();
$pred->contextid = $this->context->id;
$pred->sampleid = $course1->id;
$pred->rangeindex = 1;
$pred->prediction = 1;
$pred->predictionscore = 1;
$pred->calculations = json_encode(array('test_indicator_max' => 1));
$pred->timecreated = time();
$DB->insert_record('analytics_predictions', $pred);
$pred->sampleid = $course2->id;
$DB->insert_record('analytics_predictions', $pred);
}
/**
* test_get_predictions
*/
public function test_action_executed() {
global $DB;
$this->assertEquals(0, $DB->count_records('analytics_prediction_actions'));
// Teacher 2 flags a prediction (it doesn't matter which one) as fixed.
$this->setUser($this->teacher2);
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$prediction = reset($predictions);
$prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
$this->assertEquals(1, $DB->count_records('analytics_prediction_actions'));
$action = $DB->get_record('analytics_prediction_actions', array('userid' => $this->teacher2->id));
$this->assertEquals(\core_analytics\prediction::ACTION_FIXED, $action->actionname);
$prediction->action_executed(\core_analytics\prediction::ACTION_NOT_USEFUL, $this->model->get_target());
$this->assertEquals(2, $DB->count_records('analytics_prediction_actions'));
}
/**
* test_get_predictions
*/
public function test_get_predictions() {
// Already logged in as admin.
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$this->assertCount(2, $predictions);
$this->setUser($this->teacher1);
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$this->assertCount(2, $predictions);
$this->setUser($this->teacher2);
list($ignored, $predictions) = $this->model->get_predictions($this->context, false);
$this->assertCount(2, $predictions);
// Teacher 2 flags a prediction (it doesn't matter which one) as fixed.
$prediction = reset($predictions);
$prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$this->assertCount(1, $predictions);
list($ignored, $predictions) = $this->model->get_predictions($this->context, false);
$this->assertCount(2, $predictions);
// Teacher 1 can still see both predictions.
$this->setUser($this->teacher1);
list($ignored, $predictions) = $this->model->get_predictions($this->context, true);
$this->assertCount(2, $predictions);
list($ignored, $predictions) = $this->model->get_predictions($this->context, false);
$this->assertCount(2, $predictions);
}
}

View File

@ -53,6 +53,8 @@ $string['errorunexistingtimesplitting'] = 'The selected time-splitting method is
$string['errorunexistingmodel'] = 'Non-existing model {$a}';
$string['errorunknownaction'] = 'Unknown action';
$string['eventpredictionactionstarted'] = 'Prediction process started';
$string['eventinsightsviewed'] = 'Insights viewed';
$string['fixedack'] = 'Acknowledged / fixed';
$string['insightmessagesubject'] = 'New insight for "{$a->contextname}": {$a->insightname}';
$string['insightinfomessage'] = 'The system generated some insights for you: {$a}';
$string['insightinfomessagehtml'] = 'The system generated some insights for you: <a href="{$a}">{$a}</a>.';
@ -71,6 +73,7 @@ $string['nonewtimeranges'] = 'No new time ranges; nothing to predict.';
$string['nopredictionsyet'] = 'No predictions available yet';
$string['noranges'] = 'No predictions yet';
$string['notrainingbasedassumptions'] = 'Models based on assumptions do not need training';
$string['notuseful'] = 'Not useful';
$string['novaliddata'] = 'No valid data available';
$string['novalidsamples'] = 'No valid samples available';
$string['onlycli'] = 'Analytics processes execution via command line only';

View File

@ -61,25 +61,27 @@ class course_dropout extends \core_analytics\local\target\binary {
public function prediction_actions(\core_analytics\prediction $prediction, $includedetailsaction = false) {
global $USER;
$actions = parent::prediction_actions($prediction, $includedetailsaction);
$actions = array();
$sampledata = $prediction->get_sample_data();
$studentid = $sampledata['user']->id;
$attrs = array('target' => '_blank');
// Send a message.
$url = new \moodle_url('/message/index.php', array('user' => $USER->id, 'id' => $studentid));
$pix = new \pix_icon('t/message', get_string('sendmessage', 'message'));
$actions['studentmessage'] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix,
get_string('sendmessage', 'message'));
$actions[] = new \core_analytics\prediction_action('studentmessage', $prediction, $url, $pix,
get_string('sendmessage', 'message'), $attrs);
// View outline report.
$url = new \moodle_url('/report/outline/user.php', array('id' => $studentid, 'course' => $sampledata['course']->id,
'mode' => 'outline'));
$pix = new \pix_icon('i/report', get_string('outlinereport'));
$actions['viewoutlinereport'] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix,
get_string('outlinereport'));
$actions[] = new \core_analytics\prediction_action('viewoutlinereport', $prediction, $url, $pix,
get_string('outlinereport'), $attrs);
return $actions;
return array_merge($actions, parent::prediction_actions($prediction, $includedetailsaction));
}
/**

View File

@ -67,25 +67,28 @@ class no_teaching extends \core_analytics\local\target\binary {
require_once($CFG->dirroot . '/course/lib.php');
// No need to call the parent as the parent's action is view details and this target only have 1 feature.
$actions = array();
$sampledata = $prediction->get_sample_data();
$course = $sampledata['course'];
$actions = array();
$url = new \moodle_url('/course/view.php', array('id' => $course->id));
$pix = new \pix_icon('i/course', get_string('course'));
$actions['viewcourse'] = new \core_analytics\prediction_action('viewcourse', $prediction,
$actions[] = new \core_analytics\prediction_action('viewcourse', $prediction,
$url, $pix, get_string('view'));
if (course_can_view_participants($sampledata['context'])) {
$url = new \moodle_url('/user/index.php', array('id' => $course->id));
$pix = new \pix_icon('i/cohort', get_string('participants'));
$actions['viewparticipants'] = new \core_analytics\prediction_action('viewparticipants', $prediction,
$actions[] = new \core_analytics\prediction_action('viewparticipants', $prediction,
$url, $pix, get_string('participants'));
}
return $actions;
$parentactions = parent::prediction_actions($prediction, $includedetailsaction);
// No need to show details as there is only 1 indicator.
unset($parentactions[\core_analytics\prediction::ACTION_PREDICTION_DETAILS]);
return array_merge($actions, $parentactions);
}
/**

View File

@ -0,0 +1,79 @@
<?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/>.
/**
* Insights page viewed event.
*
* @property-read array $other {
* Extra information about event.
*
* - string modelid: The model id
* }
*
* @package core_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Event triggered after a user views the insights page.
*
* @package core_analytics
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class insights_viewed extends \core\event\base {
/**
* Set basic properties for the event.
*/
protected function init() {
$this->data['crud'] = 'r';
// It depends on the insight really.
$this->data['edulevel'] = self::LEVEL_OTHER;
}
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventinsightsviewed', 'analytics');
}
/**
* Returns non-localised event description with id's for admin use only.
*
* @return string
*/
public function get_description() {
return "The user with id '$this->userid' has viewed model '{$this->other['modelid']}' insights in " .
"context with id '{$this->data['contextid']}'";
}
/**
* Returns relevant URL.
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url('/report/insights/insights.php', array('modelid' => $this->other['modelid'],
'contextid' => $this->data['contextid']));
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20170814" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20170904" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -3699,5 +3699,22 @@
<INDEX NAME="starttime-endtime-contextid" UNIQUE="false" FIELDS="starttime, endtime, contextid"/>
</INDEXES>
</TABLE>
<TABLE NAME="analytics_prediction_actions" COMMENT="Register of user actions over predictions.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="predictionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="actionname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="predictionid" TYPE="foreign" FIELDS="predictionid" REFTABLE="analytics_predictions" REFFIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="predictionidanduseridandactionname" UNIQUE="false" FIELDS="predictionid, userid, actionname"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>

View File

@ -2436,5 +2436,35 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2017082800.00);
}
if ($oldversion < 2017090700.01) {
// Define table analytics_prediction_actions to be created.
$table = new xmldb_table('analytics_prediction_actions');
// Adding fields to table analytics_prediction_actions.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('predictionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
$table->add_field('actionname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
// Adding keys to table analytics_prediction_actions.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('predictionid', XMLDB_KEY_FOREIGN, array('predictionid'), 'analytics_predictions', array('id'));
$table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
// Adding indexes to table analytics_prediction_actions.
$table->add_index('predictionidanduseridandactionname', XMLDB_INDEX_NOTUNIQUE,
array('predictionid', 'userid', 'actionname'));
// Conditionally launch create table for analytics_prediction_actions.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2017090700.01);
}
return true;
}

View File

@ -34,16 +34,15 @@ if ($context->contextlevel < CONTEXT_COURSE) {
$PAGE->set_context($context);
}
if (empty($forwardurl)) {
$params = array('modelid' => $model->get_id(), 'contextid' => $context->id);
$forwardurl = new \moodle_url('/report/insights/insights.php', $params);
}
$params = array('predictionid' => $prediction->get_prediction_data()->id, 'action' => $actionname, 'forwardurl' => $forwardurl);
$url = new \moodle_url('/report/insights/action.php', $params);
$PAGE->set_url($url);
// Check that the provided action exists.
$actions = $model->get_target()->prediction_actions($prediction, true);
if (!isset($actions[$actionname])) {
throw new \moodle_exception('errorunknownaction', 'analytics');
}
$modelready = $model->is_enabled() && $model->is_trained() && $model->predictions_exist($context);
if (!$modelready) {
@ -58,11 +57,6 @@ if (!$modelready) {
exit(0);
}
$eventdata = array (
'context' => $context,
'objectid' => $predictionid,
'other' => array('actionname' => $actionname)
);
\core\event\prediction_action_started::create($eventdata)->trigger();
$prediction->action_executed($actionname, $model->get_target());
redirect($forwardurl);

View File

@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification"],function(a,b,c){return{init:function(d){a("a[data-prediction-methodname][data-prediction-id="+d+"]").on("click",function(e){e.preventDefault();var f=a(e.currentTarget),g=f.attr("data-prediction-methodname"),h=f.closest("tr");if(h.length>0){var i=b.call([{methodname:g,args:{predictionid:d}}])[0];i.done(function(){h[0].remove(),0===a(".insights-list tr").length&&window.history.back()}).fail(c.exception)}})}}});

View File

@ -0,0 +1,67 @@
// 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/>.
/**
* Module to manage report insights actions that are executed using AJAX.
*
* @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
*/
/**
* This module manages prediction actions that require AJAX requests.
*
* @module report_insights/actions
*/
define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
return {
/**
* Attach on click handlers to hide predictions.
*
* @param {Number} predictionId The prediction id.
* @access public
*/
init: function(predictionId) {
// Select the prediction with the provided id ensuring that an external function is set as method name.
$('a[data-prediction-methodname][data-prediction-id=' + predictionId + ']').on('click', function(e) {
e.preventDefault();
var action = $(e.currentTarget);
var methodname = action.attr('data-prediction-methodname');
var predictionContainers = action.closest('tr');
if (predictionContainers.length > 0) {
var promise = Ajax.call([
{
methodname: methodname,
args: {predictionid: predictionId}
}
])[0];
promise.done(function() {
predictionContainers[0].remove();
// Move back if no remaining predictions.
if ($('.insights-list tr').length === 0) {
window.history.back();
}
}).fail(Notification.exception);
}
});
}
};
});

View File

@ -0,0 +1,155 @@
<?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/>.
/**
* This is the external API for this component.
*
* @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
*/
namespace report_insights;
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/externallib.php");
use external_api;
use external_function_parameters;
use external_value;
use external_single_structure;
use external_warnings;
/**
* This is the external API for this component.
*
* @copyright 2017 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* set_notuseful_prediction parameters.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function set_notuseful_prediction_parameters() {
return new external_function_parameters(
array(
'predictionid' => new external_value(PARAM_INT, 'The prediction id', VALUE_REQUIRED)
)
);
}
/**
* Flags a prediction as fixed so no need to display it any more.
*
* @param int $predictionid
* @return array an array of warnings and a boolean
* @since Moodle 3.4
*/
public static function set_notuseful_prediction($predictionid) {
$params = self::validate_parameters(self::set_notuseful_prediction_parameters(), array('predictionid' => $predictionid));
list($model, $prediction, $context) = self::validate_prediction($params['predictionid']);
$prediction->action_executed(\core_analytics\prediction::ACTION_NOT_USEFUL, $model->get_target());
$success = true;
return array('success' => $success, 'warnings' => array());
}
/**
* set_notuseful_prediction return
*
* @return external_description
* @since Moodle 3.4
*/
public static function set_notuseful_prediction_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the prediction was successfully flagged as not useful.'),
'warnings' => new external_warnings(),
)
);
}
/**
* set_fixed_prediction parameters.
*
* @return external_function_parameters
* @since Moodle 3.4
*/
public static function set_fixed_prediction_parameters() {
return new external_function_parameters(
array(
'predictionid' => new external_value(PARAM_INT, 'The prediction id', VALUE_REQUIRED)
)
);
}
/**
* Flags a prediction as fixed so no need to display it any more.
*
* @param int $predictionid
* @return array an array of warnings and a boolean
* @since Moodle 3.4
*/
public static function set_fixed_prediction($predictionid) {
$params = self::validate_parameters(self::set_fixed_prediction_parameters(), array('predictionid' => $predictionid));
list($model, $prediction, $context) = self::validate_prediction($params['predictionid']);
$prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $model->get_target());
$success = true;
return array('success' => $success, 'warnings' => array());
}
/**
* set_fixed_prediction return
*
* @return external_description
* @since Moodle 3.4
*/
public static function set_fixed_prediction_returns() {
return new external_single_structure(
array(
'success' => new external_value(PARAM_BOOL, 'True if the prediction was successfully flagged as fixed.'),
'warnings' => new external_warnings(),
)
);
}
/**
* Validates access to the prediction and returns it.
*
* @param int $predictionid
* @return array array($model, $prediction, $context)
*/
protected static function validate_prediction($predictionid) {
list($model, $prediction, $context) = \core_analytics\manager::get_prediction($predictionid);
self::validate_context($context);
return array($model, $prediction, $context);
}
}

View File

@ -93,7 +93,7 @@ class insights_list implements \renderable, \templatable {
$total = 0;
if ($this->model->uses_insights()) {
$predictionsdata = $this->model->get_predictions($this->context, $this->page, $this->perpage);
$predictionsdata = $this->model->get_predictions($this->context, true, $this->page, $this->perpage);
$data->insights = array();
if ($predictionsdata) {

View File

@ -0,0 +1,47 @@
<?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/>.
/**
* Report insights webservice definitions.
*
* @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
*/
defined('MOODLE_INTERNAL') || die();
$functions = array(
'report_insights_set_notuseful_prediction' => array(
'classname' => 'report_insights\external',
'methodname' => 'set_notuseful_prediction',
'description' => 'Flags the prediction as not useful.',
'type' => 'write',
'ajax' => true,
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'report_insights_set_fixed_prediction' => array(
'classname' => 'report_insights\external',
'methodname' => 'set_fixed_prediction',
'description' => 'Flags a prediction as fixed.',
'type' => 'write',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
'ajax' => true,
)
);

View File

@ -111,4 +111,10 @@ echo $OUTPUT->header();
$renderable = new \report_insights\output\insights_list($model, $context, $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();

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017051501; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2017051502; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2017050500; // Requires this Moodle version.
$plugin->component = 'report_insights'; // Full name of the plugin (used for diagnostics).

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017090700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017090700.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.