Skip to content

Commit

Permalink
MDL-60944 analytics: Add base support for import / export
Browse files Browse the repository at this point in the history
  • Loading branch information
ankitagarwal authored and David Monllaó committed Feb 25, 2019
1 parent 07203d3 commit 349c441
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 4 deletions.
47 changes: 47 additions & 0 deletions admin/tool/analytics/classes/import_model_form.php
@@ -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/>.

/**
* Model upload form.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace tool_analytics;
defined('MOODLE_INTERNAL') || die();


/**
* Model upload form.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class import_model_form extends \moodleform {
function definition () {
$mform = $this->_form;

$mform->addElement('header', 'settingsheader', get_string('analyticsimportmodel', 'tool_analytics'));

$mform->addElement('filepicker', 'modelfile', get_string('file'), null, ['accepted_types' => '.json']);
$mform->addRule('modelfile', null, 'required');

$this->add_action_buttons(false, get_string('submit'));
}
}
11 changes: 10 additions & 1 deletion admin/tool/analytics/classes/output/models_list.php
Expand Up @@ -229,13 +229,22 @@ public function export_for_template(\renderer_base $output) {

// Export training data.
if (!$model->is_static() && $model->is_trained()) {
$urlparams['action'] = 'export';
$urlparams['action'] = 'exportdata';
$url = new \moodle_url('model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
get_string('exporttrainingdata', 'tool_analytics')), get_string('export', 'tool_analytics'));
$actionsmenu->add($icon);
}

// Export model.
if (!$model->is_static() && $model->get_indicators() && !empty($modeldata->timesplitting)) {
$urlparams['action'] = 'exportmodel';
$url = new \moodle_url('model.php', $urlparams);
$icon = new \action_menu_link_secondary($url, new \pix_icon('i/export',
get_string('exportmodel', 'tool_analytics')), get_string('exportmodel', 'tool_analytics'));
$actionsmenu->add($icon);
}

// Invalid analysables.
$analyser = $model->get_analyser(['notimesplitting' => true]);
if (!$analyser instanceof \core_analytics\local\analyser\sitewide) {
Expand Down
57 changes: 57 additions & 0 deletions admin/tool/analytics/importmodel.php
@@ -0,0 +1,57 @@
<?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/>.

/**
* Import models tool frontend.
*
* @package tool_analytics
* @copyright 2017 onwards Ankit Agarwal
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/adminlib.php');

admin_externalpage_setup('analyticsmodelimport', '', null, '', array('pagelayout' => 'report'));
echo $OUTPUT->header();

$form = new tool_analytics\import_model_form();
if ($data = $form->get_data()) {
$content = json_decode($form->get_file_content('modelfile'));
if (empty($content->moodleversion)) {
// Should never happen.
echo $OUTPUT->notification(get_string('missingmoodleversion', 'tool_analytics'), 'error');
} else {
if ($content->moodleversion != $CFG->version) {
$a = new stdClass();
$a->importedversion = $content->moodleversion;
$a->version = $CFG->version;
echo $OUTPUT->notification(get_string('versionnotsame', 'tool_analytics', $a), 'warning');
}
$model = \core_analytics\model::create_from_json($content);
if ($model) {
echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
} else {
echo $OUTPUT->notification(get_string('error'), 'error');
}
}
echo $OUTPUT->single_button(new moodle_url("$CFG->wwwroot/$CFG->admin/tool/analytics/index.php"),
get_string('continue'), 'get');
} else {
$form->display();
}

echo $OUTPUT->footer();
6 changes: 6 additions & 0 deletions admin/tool/analytics/lang/en/tool_analytics.php
Expand Up @@ -26,9 +26,11 @@
$string['allpredictions'] = 'All predictions';
$string['analysingsitedata'] = 'Analysing the site';
$string['analyticmodels'] = 'Analytics models';
$string['analyticsimportmodel'] = 'Import analytics model';
$string['bettercli'] = 'Evaluating models and generating predictions may involve heavy processing. It is recommended to run these actions from the command line.';
$string['cantguessstartdate'] = 'Can\'t guess the start date';
$string['cantguessenddate'] = 'Can\'t guess the end date';
$string['classdoesnotexist'] = 'Class {$a} does not exist';
$string['clearpredictions'] = 'Clear predictions';
$string['clearmodelpredictions'] = 'Are you sure you want to clear all "{$a}" predictions?';
$string['clienablemodel'] = 'You can enable the model by selecting a time-splitting method by its ID. Note that you can also enable it later using the web interface (\'none\' to exit).';
Expand All @@ -42,6 +44,7 @@
$string['errornoenabledandtrainedmodels'] = 'There are no enabled and trained models to predict.';
$string['errornoenabledmodels'] = 'There are no enabled models to train.';
$string['errornoexport'] = 'Only trained models can be exported';
$string['errornoexportconfg'] = 'Only non static models with timeplitting methods can be exported.';
$string['errornostaticedit'] = 'Models based on assumptions cannot be edited.';
$string['errornostaticevaluated'] = 'Models based on assumptions cannot be evaluated. They are always 100% correct according to how they were defined.';
$string['errornostaticlog'] = 'Models based on assumptions cannot be evaluated because there is no performance log.';
Expand All @@ -51,6 +54,7 @@
$string['evaluatemodel'] = 'Evaluate model';
$string['evaluationinbatches'] = 'The site contents are calculated and stored in batches. The evaluation process may be stopped at any time. The next time it is run, it will continue from the point when it was stopped.';
$string['export'] = 'Export';
$string['exportmodel'] = 'Export model configuration';
$string['exporttrainingdata'] = 'Export training data';
$string['getpredictionsresultscli'] = 'Results using {$a->name} (id: {$a->id}) course duration splitting';
$string['getpredictionsresults'] = 'Results using {$a->name} course duration splitting';
Expand All @@ -67,6 +71,7 @@
$string['invalidprediction'] = 'Invalid to get predictions';
$string['invalidtraining'] = 'Invalid to train the model';
$string['loginfo'] = 'Log extra info';
$string['missingmoodleversion'] = 'Imported file does not define a moodle version number';
$string['modelid'] = 'Model ID';
$string['modelinvalidanalysables'] = 'Invalid analysable elements for "{$a}" model';
$string['modelresults'] = '{$a} results';
Expand All @@ -92,6 +97,7 @@
$string['trainingprocessfinished'] = 'Training process finished';
$string['trainingresults'] = 'Training results';
$string['trainmodels'] = 'Train models';
$string['versionnotsame'] = 'Imported file was from a different moodle version ({$a->importedversion}) than the current one ({$a->version})';
$string['viewlog'] = 'Log';
$string['weeksenddateautomaticallyset'] = 'End date automatically set based on start date and the number of sections';
$string['weeksenddatedefault'] = 'End date automatically calculated from the course start date.';
Expand Down
26 changes: 23 additions & 3 deletions admin/tool/analytics/model.php
Expand Up @@ -23,6 +23,7 @@
*/

require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/dataformatlib.php');

$id = required_param('id', PARAM_INT);
$action = required_param('action', PARAM_ALPHANUMEXT);
Expand Down Expand Up @@ -57,8 +58,11 @@
case 'disable':
$title = get_string('disable');
break;
case 'export':
$title = get_string('export', 'tool_analytics');
case 'exportdata':
$title = get_string('exporttrainingdata', 'tool_analytics');
break;
case 'exportmodel':
$title = get_string('exportmodel', 'tool_analytics');
break;
case 'clear':
$title = get_string('clearpredictions', 'tool_analytics');
Expand Down Expand Up @@ -203,7 +207,7 @@
echo $renderer->render_table($modellogstable);
break;

case 'export':
case 'exportdata':

if ($model->is_static() || !$model->is_trained()) {
throw new moodle_exception('errornoexport', 'tool_analytics');
Expand All @@ -219,6 +223,22 @@
send_file($file, $filename, null, 0, false, true);
break;

case 'exportmodel':

if (!$model->is_static() && $model->get_indicators() && !empty($model->timesplitting)) {
throw new moodle_exception('errornoexportconfg', 'tool_analytics');
}
$downloadfilename = 'model-config.' . $model->get_id() . '.' . time() . '.json';
$modelconfig = $model->export_as_json();
make_temp_directory('analyticsexport');
$tempfilename = $CFG->tempdir .'/analyticsexport/'. md5(sesskey() . microtime() . $downloadfilename);
if (!file_put_contents($tempfilename, $modelconfig)) {
print_error('cannotcreatetempdir');
}
@header("Content-type: text/json; charset=UTF-8");
send_temp_file($tempfilename, $downloadfilename);
break;

case 'clear':
confirm_sesskey();

Expand Down
2 changes: 2 additions & 0 deletions admin/tool/analytics/settings.php
Expand Up @@ -26,3 +26,5 @@

$ADMIN->add('analytics', new admin_externalpage('analyticmodels', get_string('analyticmodels', 'tool_analytics'),
"$CFG->wwwroot/$CFG->admin/tool/analytics/index.php", 'moodle/analytics:managemodels'));
$ADMIN->add('analytics', new admin_externalpage('analyticsmodelimport', get_string('analyticsimportmodel', 'tool_analytics'),
"$CFG->wwwroot/$CFG->admin/tool/analytics/importmodel.php", 'moodle/analytics:managemodels'));
65 changes: 65 additions & 0 deletions analytics/classes/model.php
Expand Up @@ -385,6 +385,44 @@ public static function create(\core_analytics\local\target\base $target, array $
return $model;
}

/**
* Creates a new model from json configuration.
*
* @param string $json json data.
* @return \core_analytics\model
*/
public static function create_from_json($jsondata) {

\core_analytics\manager::check_can_manage_models();
if (empty($jsondata) || !isset($jsondata->target) || !isset($jsondata->indicators) || !isset($jsondata->timesplitting)) {
throw new \coding_exception("invalid json data");
}

// Target.
$target = $jsondata->target;
if (!class_exists($target)) {
throw new \moodle_exception('classdoesnotexist', 'tool_analytics', $target);
}
$target = \core_analytics\manager::get_target($target);

// Indicators.
$indicators = [];
foreach($jsondata->indicators as $indicator) {
if (!class_exists($indicator)) {
throw new \moodle_exception('classdoesnotexist', 'tool_analytics', $indicator);
}
$indicators[] = \core_analytics\manager::get_indicator($indicator);
}

// Timesplitting.
$timesplitting = $jsondata->timesplitting;
if (!class_exists($timesplitting)) {
throw new \moodle_exception('classdoesnotexist', 'tool_analytics', $timesplitting);
}

return self::create($target, $indicators, $timesplitting);
}

/**
* Does this model exist?
*
Expand Down Expand Up @@ -1379,6 +1417,33 @@ public function export() {
return $data;
}

/**
* Exports the model data as JSON.
*
* @return string JSON encoded data.
*/
public function export_as_json() {
global $CFG;

$data = new \stdClass();
$data->target = $this->get_target()->get_id();


if ($timesplitting = $this->get_time_splitting()) {
$data->timesplitting = $timesplitting->get_id();
} else {
// We don't want to allow models without timesplitting to be exported.
throw new \moodle_exception('errornotimesplittings', 'analytics');
}

$data->indicators = [];
foreach ($this->get_indicators() as $indicator) {
$data->indicators[] = $indicator->get_id();
}
$data->moodleversion = $CFG->version;
return json_encode($data);
}

/**
* Returns the model logs data.
*
Expand Down
30 changes: 30 additions & 0 deletions analytics/tests/model_test.php
Expand Up @@ -317,6 +317,36 @@ public function test_model_timelimit() {
$this->assertLessThanOrEqual(2, $DB->count_records('analytics_used_analysables', $params));
}

/**
* Test export_as_json() API.
*/
public function test_export_as_json() {
global $CFG;
$this->resetAfterTest(true);

$this->model->enable('\core\analytics\time_splitting\quarters');
$obj = json_decode($this->model->export_as_json());
$this->assertSame($CFG->version, $obj->moodleversion);
$this->assertSame($this->modelobj->target, $obj->target);
$this->assertSame(json_decode($this->modelobj->indicators), $obj->indicators);
$this->assertSame($this->modelobj->timesplitting, $obj->timesplitting);
}

/**
* Test export_from_json() API.
*/
public function test_create_from_json() {
global $CFG;
$this->resetAfterTest(true);

$this->model->enable('\core\analytics\time_splitting\quarters');
$json = $this->model->export_as_json();
$obj = \core_analytics\model::create_from_json(json_decode($json))->get_model_obj();
$this->assertSame($this->modelobj->target, $obj->target);
$this->assertSame($this->modelobj->indicators, $obj->indicators);
$this->assertSame($this->modelobj->timesplitting, $obj->timesplitting);
}

/**
* Generates a model log record.
*/
Expand Down

0 comments on commit 349c441

Please sign in to comment.