Merge branch 'MDL-59067_master' of git://github.com/dmonllao/moodle
This commit is contained in:
@@ -119,6 +119,9 @@ class dataset_manager {
|
||||
* @return bool Could we get the lock or not.
|
||||
*/
|
||||
public function init_process() {
|
||||
|
||||
// Do not include $this->includetarget as we don't want the same analysable to be analysed for training
|
||||
// and prediction at the same time.
|
||||
$lockkey = 'modelid:' . $this->modelid . '-analysableid:' . $this->analysableid .
|
||||
'-timesplitting:' . self::clean_time_splitting_id($this->timesplittingid);
|
||||
|
||||
|
||||
@@ -47,6 +47,13 @@ abstract class base {
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* A $this->$target copy loaded with the ongoing analysis analysable.
|
||||
*
|
||||
* @var \core_analytics\local\target\base
|
||||
*/
|
||||
protected $analysabletarget;
|
||||
|
||||
/**
|
||||
* The model indicators.
|
||||
*
|
||||
@@ -255,11 +262,11 @@ abstract class base {
|
||||
|
||||
// Target instances scope is per-analysable (it can't be lower as calculations run once per
|
||||
// analysable, not time splitting method nor time range).
|
||||
$target = call_user_func(array($this->target, 'instance'));
|
||||
$this->analysabletarget = call_user_func(array($this->target, 'instance'));
|
||||
|
||||
// We need to check that the analysable is valid for the target even if we don't include targets
|
||||
// as we still need to discard invalid analysables for the target.
|
||||
$result = $target->is_valid_analysable($analysable, $includetarget);
|
||||
$result = $this->analysabletarget->is_valid_analysable($analysable, $includetarget);
|
||||
if ($result !== true) {
|
||||
$a = new \stdClass();
|
||||
$a->analysableid = $analysable->get_id();
|
||||
@@ -291,11 +298,7 @@ abstract class base {
|
||||
}
|
||||
}
|
||||
|
||||
if ($includetarget) {
|
||||
$result = $this->process_time_splitting($timesplitting, $analysable, $target);
|
||||
} else {
|
||||
$result = $this->process_time_splitting($timesplitting, $analysable);
|
||||
}
|
||||
$result = $this->process_time_splitting($timesplitting, $analysable, $includetarget);
|
||||
|
||||
if (!empty($result->file)) {
|
||||
$files[$timesplitting->get_id()] = $result->file;
|
||||
@@ -342,10 +345,10 @@ abstract class base {
|
||||
*
|
||||
* @param \core_analytics\local\time_splitting\base $timesplitting
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param \core_analytics\local\target\base|false $target
|
||||
* @param bool $includetarget
|
||||
* @return \stdClass Results object.
|
||||
*/
|
||||
protected function process_time_splitting($timesplitting, $analysable, $target = false) {
|
||||
protected function process_time_splitting($timesplitting, $analysable, $includetarget = false) {
|
||||
|
||||
$result = new \stdClass();
|
||||
|
||||
@@ -372,7 +375,7 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($target) {
|
||||
if ($includetarget) {
|
||||
// All ranges are used when we are calculating data for training.
|
||||
$ranges = $timesplitting->get_all_ranges();
|
||||
} else {
|
||||
@@ -399,7 +402,7 @@ abstract class base {
|
||||
}
|
||||
|
||||
// Only when processing data for predictions.
|
||||
if ($target === false) {
|
||||
if (!$includetarget) {
|
||||
// We also filter out samples and ranges that have already been used for predictions.
|
||||
$this->filter_out_prediction_samples_and_ranges($sampleids, $ranges, $timesplitting);
|
||||
}
|
||||
@@ -433,10 +436,9 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Remove samples the target consider invalid. Note that we use $this->target, $target will be false
|
||||
// during prediction, but we still need to discard samples the target considers invalid.
|
||||
$this->target->add_sample_data($samplesdata);
|
||||
$this->target->filter_out_invalid_samples($sampleids, $analysable, $target);
|
||||
// Remove samples the target consider invalid.
|
||||
$this->analysabletarget->add_sample_data($samplesdata);
|
||||
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
|
||||
|
||||
if (!$sampleids) {
|
||||
$result->status = \core_analytics\model::NO_DATASET;
|
||||
@@ -450,15 +452,15 @@ abstract class base {
|
||||
// indicator to calculate the sample.
|
||||
$this->indicators[$key]->add_sample_data($samplesdata);
|
||||
}
|
||||
// Provide samples to the target instance (different than $this->target) $target is the new instance we get
|
||||
// for each analysis in progress.
|
||||
if ($target) {
|
||||
$target->add_sample_data($samplesdata);
|
||||
}
|
||||
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges, $target);
|
||||
if ($includetarget) {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
|
||||
$this->analysabletarget);
|
||||
} else {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$result->status = \core_analytics\model::ANALYSABLE_REJECTED_TIME_SPLITTING_METHOD;
|
||||
@@ -477,7 +479,7 @@ abstract class base {
|
||||
if ($this->options['evaluation'] === false) {
|
||||
// Save the samples that have been already analysed so they are not analysed again in future.
|
||||
|
||||
if ($target) {
|
||||
if ($includetarget) {
|
||||
$this->save_train_samples($sampleids, $timesplitting, $file);
|
||||
} else {
|
||||
$this->save_prediction_samples($sampleids, $ranges, $timesplitting);
|
||||
|
||||
@@ -130,18 +130,25 @@ abstract class base extends \core_analytics\calculable {
|
||||
* @param string $samplesorigin
|
||||
* @param integer $starttime Limit the calculation to this timestart
|
||||
* @param integer $endtime Limit the calculation to this timeend
|
||||
* @return array The format to follow is [userid] = int|float[]
|
||||
* @param array $existingcalculations Existing calculations of this indicator, indexed by sampleid.
|
||||
* @return array array[0] with format [sampleid] = int[]|float[], array[1] with format [sampleid] = int|float
|
||||
*/
|
||||
public function calculate($sampleids, $samplesorigin, $starttime = false, $endtime = false) {
|
||||
public function calculate($sampleids, $samplesorigin, $starttime = false, $endtime = false, $existingcalculations = array()) {
|
||||
|
||||
if (!PHPUNIT_TEST && CLI_SCRIPT) {
|
||||
echo '.';
|
||||
}
|
||||
|
||||
$calculations = array();
|
||||
$newcalculations = array();
|
||||
foreach ($sampleids as $sampleid => $unusedsampleid) {
|
||||
|
||||
$calculatedvalue = $this->calculate_sample($sampleid, $samplesorigin, $starttime, $endtime);
|
||||
if (isset($existingcalculations[$sampleid])) {
|
||||
$calculatedvalue = $existingcalculations[$sampleid];
|
||||
} else {
|
||||
$calculatedvalue = $this->calculate_sample($sampleid, $samplesorigin, $starttime, $endtime);
|
||||
$newcalculations[$sampleid] = $calculatedvalue;
|
||||
}
|
||||
|
||||
if (!is_null($calculatedvalue) && ($calculatedvalue > self::MAX_VALUE || $calculatedvalue < self::MIN_VALUE)) {
|
||||
throw new \coding_exception('Calculated values should be higher than ' . self::MIN_VALUE .
|
||||
@@ -151,8 +158,8 @@ abstract class base extends \core_analytics\calculable {
|
||||
$calculations[$sampleid] = $calculatedvalue;
|
||||
}
|
||||
|
||||
$calculations = $this->to_features($calculations);
|
||||
$features = $this->to_features($calculations);
|
||||
|
||||
return $calculations;
|
||||
return array($features, $newcalculations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,11 @@ abstract class base {
|
||||
*/
|
||||
protected static $indicators = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $evaluation = false;
|
||||
|
||||
/**
|
||||
* Define the time splitting methods ranges.
|
||||
*
|
||||
@@ -96,6 +101,24 @@ abstract class base {
|
||||
return '\\' . get_class($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current evaluation value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_evaluating() {
|
||||
return $this->evaluation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the evaluation flag.
|
||||
*
|
||||
* @param bool $evaluation
|
||||
*/
|
||||
public function set_evaluating($evaluation) {
|
||||
$this->evaluation = (bool)$evaluation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the analysable and updates the time ranges according to the analysable start and end dates.
|
||||
*
|
||||
@@ -196,10 +219,20 @@ abstract class base {
|
||||
* @return array
|
||||
*/
|
||||
protected function calculate_indicators($sampleids, $samplesorigin, $indicators, $ranges) {
|
||||
global $DB;
|
||||
|
||||
$dataset = array();
|
||||
|
||||
// Faster to run 1 db query per range.
|
||||
$existingcalculations = array();
|
||||
foreach ($ranges as $rangeindex => $range) {
|
||||
// Load existing calculations.
|
||||
$existingcalculations[$rangeindex] = \core_analytics\manager::get_indicator_calculations($this->analysable,
|
||||
$range['start'], $range['end'], $samplesorigin);
|
||||
}
|
||||
|
||||
// Fill the dataset samples with indicators data.
|
||||
$newcalculations = array();
|
||||
foreach ($indicators as $indicator) {
|
||||
|
||||
// Per-range calculations.
|
||||
@@ -208,11 +241,17 @@ abstract class base {
|
||||
// Indicator instances are per-range.
|
||||
$rangeindicator = clone $indicator;
|
||||
|
||||
// Calculate the indicator for each sample in this time range.
|
||||
$calculated = $rangeindicator->calculate($sampleids, $samplesorigin, $range['start'], $range['end']);
|
||||
$prevcalculations = array();
|
||||
if (!empty($existingcalculations[$rangeindex][$rangeindicator->get_id()])) {
|
||||
$prevcalculations = $existingcalculations[$rangeindex][$rangeindicator->get_id()];
|
||||
}
|
||||
|
||||
// Copy the calculated data to the dataset.
|
||||
foreach ($calculated as $analysersampleid => $calculatedvalues) {
|
||||
// Calculate the indicator for each sample in this time range.
|
||||
list($samplesfeatures, $newindicatorcalculations) = $rangeindicator->calculate($sampleids,
|
||||
$samplesorigin, $range['start'], $range['end'], $prevcalculations);
|
||||
|
||||
// Copy the features data to the dataset.
|
||||
foreach ($samplesfeatures as $analysersampleid => $features) {
|
||||
|
||||
$uniquesampleid = $this->append_rangeindex($analysersampleid, $rangeindex);
|
||||
|
||||
@@ -221,10 +260,48 @@ abstract class base {
|
||||
$dataset[$uniquesampleid] = array();
|
||||
}
|
||||
|
||||
// Append the calculated indicator features at the end of the sample.
|
||||
$dataset[$uniquesampleid] = array_merge($dataset[$uniquesampleid], $calculatedvalues);
|
||||
// Append the features indicator features at the end of the sample.
|
||||
$dataset[$uniquesampleid] = array_merge($dataset[$uniquesampleid], $features);
|
||||
}
|
||||
|
||||
if (!$this->is_evaluating()) {
|
||||
$timecreated = time();
|
||||
foreach ($newindicatorcalculations as $sampleid => $calculatedvalue) {
|
||||
// Prepare the new calculations to be stored into DB.
|
||||
|
||||
$indcalc = new \stdClass();
|
||||
$indcalc->contextid = $this->analysable->get_context()->id;
|
||||
$indcalc->starttime = $range['start'];
|
||||
$indcalc->endtime = $range['end'];
|
||||
$indcalc->sampleid = $sampleid;
|
||||
$indcalc->sampleorigin = $samplesorigin;
|
||||
$indcalc->indicator = $rangeindicator->get_id();
|
||||
$indcalc->value = $calculatedvalue;
|
||||
$indcalc->timecreated = $timecreated;
|
||||
$newcalculations[] = $indcalc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->is_evaluating()) {
|
||||
$batchsize = self::get_insert_batch_size();
|
||||
if (count($newcalculations) > $batchsize) {
|
||||
// We don't want newcalculations array to grow too much as we already keep the
|
||||
// system memory busy storing $dataset contents.
|
||||
|
||||
// Insert from the beginning.
|
||||
$remaining = array_splice($newcalculations, $batchsize);
|
||||
|
||||
// Sorry mssql and oracle, this will be slow.
|
||||
$DB->insert_records('analytics_indicator_calc', $newcalculations);
|
||||
$newcalculations = $remaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->is_evaluating() && $newcalculations) {
|
||||
// Insert the remaining records.
|
||||
$DB->insert_records('analytics_indicator_calc', $newcalculations);
|
||||
}
|
||||
|
||||
return $dataset;
|
||||
@@ -411,4 +488,32 @@ abstract class base {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch size used for insert_records.
|
||||
*
|
||||
* This method tries to find the best batch size without getting
|
||||
* into dml internals. Maximum 1000 records to save memory.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function get_insert_batch_size() {
|
||||
global $DB;
|
||||
|
||||
// 500 is pgsql default so using 1000 is fine, no other db driver uses a hardcoded value.
|
||||
if (empty($DB->dboptions['bulkinsertsize'])) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
$bulkinsert = $DB->dboptions['bulkinsertsize'];
|
||||
if ($bulkinsert < 1000) {
|
||||
return $bulkinsert;
|
||||
}
|
||||
|
||||
while ($bulkinsert > 1000) {
|
||||
$bulkinsert = round($bulkinsert / 2, 0);
|
||||
}
|
||||
|
||||
return (int)$bulkinsert;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,6 +337,32 @@ class manager {
|
||||
return $logstore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this analysable calculations during the provided period.
|
||||
*
|
||||
* @param \core_analytics\analysable $analysable
|
||||
* @param int $starttime
|
||||
* @param int $endtime
|
||||
* @param string $samplesorigin The samples origin as sampleid is not unique across models.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_indicator_calculations($analysable, $starttime, $endtime, $samplesorigin) {
|
||||
global $DB;
|
||||
|
||||
$params = array('starttime' => $starttime, 'endtime' => $endtime, 'contextid' => $analysable->get_context()->id,
|
||||
'sampleorigin' => $samplesorigin);
|
||||
$calculations = $DB->get_recordset('analytics_indicator_calc', $params, '', 'indicator, sampleid, value');
|
||||
|
||||
$existingcalculations = array();
|
||||
foreach ($calculations as $calculation) {
|
||||
if (empty($existingcalculations[$calculation->indicator])) {
|
||||
$existingcalculations[$calculation->indicator] = array();
|
||||
}
|
||||
$existingcalculations[$calculation->indicator][$calculation->sampleid] = $calculation->value;
|
||||
}
|
||||
return $existingcalculations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the models with insights at the provided context.
|
||||
*
|
||||
|
||||
@@ -293,6 +293,12 @@ class model {
|
||||
throw new \moodle_exception('errornotimesplittings', 'analytics');
|
||||
}
|
||||
|
||||
if (!empty($options['evaluation'])) {
|
||||
foreach ($timesplittings as $timesplitting) {
|
||||
$timesplitting->set_evaluating(true);
|
||||
}
|
||||
}
|
||||
|
||||
$classname = $target->get_analyser_class();
|
||||
if (!class_exists($classname)) {
|
||||
throw new \coding_exception($classname . ' class does not exists');
|
||||
|
||||
@@ -107,10 +107,11 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
* @dataProvider provider_ml_training_and_prediction
|
||||
* @param string $timesplittingid
|
||||
* @param int $predictedrangeindex
|
||||
* @param int $nranges
|
||||
* @param string $predictionsprocessorclass
|
||||
* @return void
|
||||
*/
|
||||
public function test_ml_training_and_prediction($timesplittingid, $predictedrangeindex, $predictionsprocessorclass) {
|
||||
public function test_ml_training_and_prediction($timesplittingid, $predictedrangeindex, $nranges, $predictionsprocessorclass) {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
@@ -153,6 +154,10 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, $model->is_enabled());
|
||||
$this->assertEquals(1, $model->is_trained());
|
||||
|
||||
// 20 courses * the 3 model indicators * the number of time ranges of this time splitting method.
|
||||
$indicatorcalc = 20 * 3 * $nranges;
|
||||
$this->assertEquals($indicatorcalc, $DB->count_records('analytics_indicator_calc'));
|
||||
|
||||
// 1 training file was created.
|
||||
$trainedsamples = $DB->get_records('analytics_train_samples', array('modelid' => $model->get_id()));
|
||||
$this->assertCount(1, $trainedsamples);
|
||||
@@ -260,8 +265,8 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
*/
|
||||
public function provider_ml_training_and_prediction() {
|
||||
$cases = array(
|
||||
'no_splitting' => array('\core\analytics\time_splitting\no_splitting', 0),
|
||||
'quarters' => array('\core\analytics\time_splitting\quarters', 3)
|
||||
'no_splitting' => array('\core\analytics\time_splitting\no_splitting', 0, 1),
|
||||
'quarters' => array('\core\analytics\time_splitting\quarters', 3, 4)
|
||||
);
|
||||
|
||||
// We need to test all system prediction processors.
|
||||
@@ -334,6 +339,28 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* test_read_indicator_calculations
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_read_indicator_calculations() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$starttime = 123;
|
||||
$endtime = 321;
|
||||
$sampleorigin = 'whatever';
|
||||
|
||||
$indicator = $this->getMockBuilder('test_indicator_max')->setMethods(['calculate_sample'])->getMock();
|
||||
$indicator->expects($this->never())->method('calculate_sample');
|
||||
|
||||
$existingcalcs = array(111 => 1, 222 => 0.5);
|
||||
$sampleids = array(111 => 111, 222 => 222);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, $sampleorigin, $starttime, $endtime, $existingcalcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* provider_ml_test_evaluation
|
||||
*
|
||||
|
||||
+21
-2
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20170721" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20170801" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@@ -3674,5 +3674,24 @@
|
||||
<INDEX NAME="modelidandfileidandaction" UNIQUE="false" FIELDS="modelid, fileid, action" COMMENT="Index on modelid and fileid and action"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="analytics_indicator_calc" COMMENT="Stored indicator calculations">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="starttime" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="endtime" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="sampleorigin" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="sampleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="indicator" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="value" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="2" COMMENT="The calculated value, it can be null."/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
</KEYS>
|
||||
<INDEXES>
|
||||
<INDEX NAME="starttime-endtime-contextid" UNIQUE="false" FIELDS="starttime, endtime, contextid"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
</TABLES>
|
||||
</XMLDB>
|
||||
</XMLDB>
|
||||
|
||||
@@ -2322,5 +2322,36 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2017082200.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2017082200.01) {
|
||||
|
||||
// Define table analytics_indicator_calc to be created.
|
||||
$table = new xmldb_table('analytics_indicator_calc');
|
||||
|
||||
// Adding fields to table analytics_indicator_calc.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('starttime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('endtime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('sampleorigin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('sampleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('indicator', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('value', XMLDB_TYPE_NUMBER, '10, 2', null, null, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
|
||||
// Adding keys to table analytics_indicator_calc.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
|
||||
// Adding indexes to table analytics_indicator_calc.
|
||||
$table->add_index('starttime-endtime-contextid', XMLDB_INDEX_NOTUNIQUE, array('starttime', 'endtime', 'contextid'));
|
||||
|
||||
// Conditionally launch create table for analytics_indicator_calc.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2017082200.01);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,13 +80,13 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
$data[$user2->id]['user'] = $user2;
|
||||
$indicator->add_sample_data($data);
|
||||
|
||||
$values = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
\logstore_standard\event\unittest_executed::create(
|
||||
array('context' => $coursecontext, 'userid' => $user1->id))->trigger();
|
||||
$values = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
$this->assertEquals($indicator::get_max_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -112,13 +112,13 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
$data[$user2->id]['user'] = $user2;
|
||||
$indicator->add_sample_data($data);
|
||||
|
||||
$values = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
\logstore_standard\event\unittest_executed::create(
|
||||
array('context' => $coursecontext, 'userid' => $user1->id))->trigger();
|
||||
$values = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'notrelevanthere');
|
||||
$this->assertEquals($indicator::get_max_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -141,7 +141,7 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
$data[$user2->id]['user'] = $user2;
|
||||
$indicator->add_sample_data($data);
|
||||
|
||||
$values = $indicator->calculate($sampleids, 'user');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user');
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -150,7 +150,7 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
|
||||
\logstore_standard\event\unittest_executed::create(
|
||||
array('context' => $coursecontext1, 'userid' => $user1->id))->trigger();
|
||||
$values = $indicator->calculate($sampleids, 'user');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user');
|
||||
$this->assertEquals($indicator::get_max_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -171,7 +171,7 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
|
||||
// Limited by time to avoid previous logs interfering as other logs
|
||||
// have been generated by the system.
|
||||
$values = $indicator->calculate($sampleids, 'course', $beforecourseeventcreate);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'course', $beforecourseeventcreate);
|
||||
$this->assertEquals($indicator::get_max_value(), $values[$course1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$course2->id][0]);
|
||||
|
||||
@@ -197,21 +197,21 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
$enddate = time() + (WEEKSECS * 2);
|
||||
|
||||
$this->setAdminUser();
|
||||
$values = $indicator->calculate($sampleids, 'user');
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user');
|
||||
$this->assertNull($values[$user1->id][0]);
|
||||
$this->assertNull($values[$user1->id][1]);
|
||||
$this->assertNull($values[$user1->id][0]);
|
||||
$this->assertNull($values[$user2->id][1]);
|
||||
|
||||
// Zero score for 0 accesses.
|
||||
$values = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
// 1/3 score for more than 0 accesses.
|
||||
\core\event\course_viewed::create(
|
||||
array('context' => $coursecontext, 'userid' => $user1->id))->trigger();
|
||||
$values = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
$this->assertEquals(-0.33, $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -220,7 +220,7 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
\core\event\course_viewed::create(
|
||||
array('context' => $coursecontext, 'userid' => $user1->id))->trigger();
|
||||
}
|
||||
$values = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
$this->assertEquals(0.33, $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
@@ -229,7 +229,7 @@ class core_analytics_indicators_testcase extends advanced_testcase {
|
||||
\core\event\course_viewed::create(
|
||||
array('context' => $coursecontext, 'userid' => $user1->id))->trigger();
|
||||
}
|
||||
$values = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
list($values, $unused) = $indicator->calculate($sampleids, 'user', $startdate, $enddate);
|
||||
$this->assertEquals($indicator::get_max_value(), $values[$user1->id][0]);
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$user2->id][0]);
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2017082200.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2017082200.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user