mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 06:18:28 +01:00
Merge branch 'MDL-58835_master' of git://github.com/dmonllao/moodle
This commit is contained in:
commit
8e085aacc0
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
140
analytics/tests/prediction_actions_test.php
Normal file
140
analytics/tests/prediction_actions_test.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
79
lib/classes/event/insights_viewed.php
Normal file
79
lib/classes/event/insights_viewed.php
Normal 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']));
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
1
report/insights/amd/build/actions.min.js
vendored
Normal file
1
report/insights/amd/build/actions.min.js
vendored
Normal 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)}})}}});
|
67
report/insights/amd/src/actions.js
Normal file
67
report/insights/amd/src/actions.js
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
155
report/insights/classes/external.php
Normal file
155
report/insights/classes/external.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
47
report/insights/db/services.php
Normal file
47
report/insights/db/services.php
Normal 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,
|
||||
)
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user