MDL-64739 analytics: Contexts autocomplete with ajax
authorDavid Monllaó <davidm@moodle.com>
Mon, 21 Oct 2019 05:20:13 +0000 (13:20 +0800)
committerDavid Monllaó <davidm@moodle.com>
Mon, 21 Oct 2019 07:33:29 +0000 (15:33 +0800)
13 files changed:
admin/tool/analytics/amd/build/potential-contexts.min.js [new file with mode: 0644]
admin/tool/analytics/amd/build/potential-contexts.min.js.map [new file with mode: 0644]
admin/tool/analytics/amd/src/potential-contexts.js [new file with mode: 0644]
admin/tool/analytics/classes/external.php [new file with mode: 0644]
admin/tool/analytics/classes/output/form/edit_model.php
admin/tool/analytics/createmodel.php
admin/tool/analytics/db/services.php [new file with mode: 0644]
admin/tool/analytics/model.php
admin/tool/analytics/tests/external_test.php [new file with mode: 0644]
admin/tool/analytics/version.php
analytics/classes/local/analyser/base.php
analytics/classes/manager.php
analytics/tests/manager_test.php

diff --git a/admin/tool/analytics/amd/build/potential-contexts.min.js b/admin/tool/analytics/amd/build/potential-contexts.min.js
new file mode 100644 (file)
index 0000000..c93ee47
Binary files /dev/null and b/admin/tool/analytics/amd/build/potential-contexts.min.js differ
diff --git a/admin/tool/analytics/amd/build/potential-contexts.min.js.map b/admin/tool/analytics/amd/build/potential-contexts.min.js.map
new file mode 100644 (file)
index 0000000..03b6fb5
Binary files /dev/null and b/admin/tool/analytics/amd/build/potential-contexts.min.js.map differ
diff --git a/admin/tool/analytics/amd/src/potential-contexts.js b/admin/tool/analytics/amd/src/potential-contexts.js
new file mode 100644 (file)
index 0000000..eb35367
--- /dev/null
@@ -0,0 +1,63 @@
+// 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/>.
+
+/**
+ * Potential contexts selector module.
+ *
+ * @module     tool_analytics/potential-contexts
+ * @class      potential-contexts
+ * @package    tool_analytics
+ * @copyright  2019 David Monllao
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/ajax'], function($, Ajax) {
+
+    return /** @alias module:tool_analytics/potential-contexts */ {
+
+        processResults: function(selector, results) {
+            var contexts = [];
+            if ($.isArray(results)) {
+                $.each(results, function(index, context) {
+                    contexts.push({
+                        value: context.id,
+                        label: context.name
+                    });
+                });
+                return contexts;
+
+            } else {
+                return results;
+            }
+        },
+
+        transport: function(selector, query, success, failure) {
+            var promise;
+
+            let modelid = $(selector).attr('modelid') || null;
+            promise = Ajax.call([{
+                methodname: 'tool_analytics_potential_contexts',
+                args: {
+                    query: query,
+                    modelid: modelid
+                }
+            }]);
+
+            promise[0].then(success).fail(failure);
+        }
+
+    };
+
+});
diff --git a/admin/tool/analytics/classes/external.php b/admin/tool/analytics/classes/external.php
new file mode 100644 (file)
index 0000000..515c08a
--- /dev/null
@@ -0,0 +1,113 @@
+<?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    tool_analytics
+ * @copyright  2019 David Monllao {@link http://www.davidmonllao.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_analytics;
+
+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_multiple_structure;
+
+/**
+ * This is the external API for this component.
+ *
+ * @copyright  2019 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 {
+
+    const MAX_CONTEXTS_RETURNED = 100;
+
+    /**
+     * potential_contexts parameters.
+     *
+     * @since  Moodle 3.8
+     * @return external_function_parameters
+     */
+    public static function potential_contexts_parameters() {
+        return new external_function_parameters(
+            array(
+                'query' => new external_value(PARAM_NOTAGS, 'The model id', VALUE_DEFAULT),
+                'modelid' => new external_value(PARAM_INT, 'The model id', VALUE_DEFAULT)
+            )
+        );
+    }
+
+    /**
+     * Return the contexts that match the provided query.
+     *
+     * @since  Moodle 3.8
+     * @param  string|null $query
+     * @param  int|null $modelid
+     * @return array an array of contexts
+     */
+    public static function potential_contexts(?string $query = null, ?int $modelid = null) {
+
+        $params = self::validate_parameters(self::potential_contexts_parameters(), ['modelid' => $modelid, 'query' => $query]);
+
+        \core_analytics\manager::check_can_manage_models();
+
+        if ($params['modelid']) {
+            $model = new \core_analytics\model($params['modelid']);
+            $contexts = ($model->get_analyser(['notimesplitting' => true]))::potential_context_restrictions($params['query']);
+        } else {
+            $contexts = \core_analytics\manager::get_potential_context_restrictions(null, $params['query']);
+        }
+
+        $contextoptions = [];
+        $i = 0;
+        foreach ($contexts as $contextid => $contextname) {
+
+            if ($i === self::MAX_CONTEXTS_RETURNED) {
+                // Limited to MAX_CONTEXTS_RETURNED items.
+                break;
+            }
+
+            $contextoptions[] = ['id' => $contextid, 'name' => $contextname];
+            $i++;
+        }
+
+        return $contextoptions;
+    }
+
+    /**
+     * potential_contexts return
+     *
+     * @since  Moodle 3.8
+     * @return external_description
+     */
+    public static function potential_contexts_returns() {
+        return new external_multiple_structure(
+            new external_single_structure([
+                'id'    => new external_value(PARAM_INT, 'ID of the context'),
+                'name'  => new external_value(PARAM_NOTAGS, 'The context name')
+            ])
+        );
+    }
+}
index 731d829..b9515cd 100644 (file)
@@ -106,12 +106,23 @@ class edit_model extends \moodleform {
         $mform->addHelpButton('timesplitting', 'timesplittingmethod', 'analytics');
 
         // Contexts restriction.
-        if (!empty($this->_customdata['contexts'])) {
+        if (!empty($this->_customdata['supportscontexts'])) {
 
-            \core_collator::asort($this->_customdata['contexts']);
-            $options = ['multiple' => true, 'noselectionstring' => get_string('all')];
-            $mform->addElement('autocomplete', 'contexts', get_string('contexts', 'tool_analytics'), $this->_customdata['contexts'],
-                $options);
+            $options = [
+                'ajax' => 'tool_analytics/potential-contexts',
+                'multiple' => true,
+                'noselectionstring' => get_string('all')
+            ];
+
+            if (!empty($this->_customdata['id'])) {
+                $options['modelid'] = $this->_customdata['id'];
+                $contexts = $this->load_current_contexts();
+            } else {
+                // No need to preload any selected contexts.
+                $contexts = [];
+            }
+
+            $mform->addElement('autocomplete', 'contexts', get_string('contexts', 'tool_analytics'), $contexts, $options);
             $mform->setType('contexts', PARAM_INT);
             $mform->addHelpButton('contexts', 'contexts', 'tool_analytics');
         }
@@ -207,4 +218,18 @@ class edit_model extends \moodleform {
 
         return $errors;
     }
+
+    /**
+     * Load the currently selected context options.
+     *
+     * @return array
+     */
+    protected function load_current_contexts() {
+        $contexts = [];
+        foreach ($this->_customdata['contexts'] as $context) {
+            $contexts[$context->id] = $context->get_context_name(true, true);
+        }
+
+        return $contexts;
+    }
 }
index b2d61cb..678eec8 100644 (file)
@@ -45,6 +45,8 @@ $targets = array_filter(\core_analytics\manager::get_all_targets(), function($ta
     return (!$target->based_on_assumptions());
 });
 
+// Set 'supportscontexts' to true as at this stage we don't know if the contexts are supported by
+// the selected target.
 $customdata = array(
     'trainedmodel' => false,
     'staticmodel' => false,
@@ -52,7 +54,7 @@ $customdata = array(
     'indicators' => \core_analytics\manager::get_all_indicators(),
     'timesplittings' => \core_analytics\manager::get_all_time_splittings(),
     'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors(),
-    'contexts' => \core_analytics\manager::get_potential_context_restrictions(),
+    'supportscontexts' => true,
 );
 $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
 
diff --git a/admin/tool/analytics/db/services.php b/admin/tool/analytics/db/services.php
new file mode 100644 (file)
index 0000000..6d7d0b8
--- /dev/null
@@ -0,0 +1,38 @@
+<?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/>.
+
+/**
+ * Tool analytics webservice definitions.
+ *
+ * @package    tool_analytics
+ * @copyright  2019 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(
+
+    'tool_analytics_potential_contexts' => array(
+        'classname'   => 'tool_analytics\external',
+        'methodname'  => 'potential_contexts',
+        'description' => 'Retrieve the list of potential contexts for a model.',
+        'type'        => 'read',
+        'ajax'          => true,
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
+    ),
+);
+
index 65a600e..3788f15 100644 (file)
@@ -122,6 +122,7 @@ switch ($action) {
 
         $invalidcurrenttimesplitting = $model->invalid_timesplitting_selected();
         $potentialtimesplittings = $model->get_potential_timesplittings();
+        $analyser = $model->get_analyser();
 
         $customdata = array(
             'id' => $model->get_id(),
@@ -133,7 +134,8 @@ switch ($action) {
             'indicators' => $model->get_potential_indicators(),
             'timesplittings' => $potentialtimesplittings,
             'predictionprocessors' => \core_analytics\manager::get_all_prediction_processors(),
-            'contexts' => ($model->get_analyser())::potential_context_restrictions()
+            'supportscontexts' => ($analyser)::context_restriction_support(),
+            'contexts' => $model->get_contexts(),
         );
         $mform = new \tool_analytics\output\form\edit_model(null, $customdata);
 
diff --git a/admin/tool/analytics/tests/external_test.php b/admin/tool/analytics/tests/external_test.php
new file mode 100644 (file)
index 0000000..ff26c64
--- /dev/null
@@ -0,0 +1,80 @@
+<?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/>.
+
+/**
+ * Tool analytics external functions tests.
+ *
+ * @package    tool_analytics
+ * @category   external
+ * @copyright  2019 David Monllaó {@link http://www.davidmonllao.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.8
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+require_once(__DIR__ . '/../../../../analytics/tests/fixtures/test_indicator_max.php');
+require_once(__DIR__ . '/../../../../analytics/tests/fixtures/test_target_course_level_shortname.php');
+
+/**
+ * Tool analytics external functions tests
+ *
+ * @package    tool_analytics
+ * @category   external
+ * @copyright  2019 David Monllaó {@link http://www.davidmonllao.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      Moodle 3.8
+ */
+class tool_analytics_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * test_potential_contexts description
+     */
+    public function test_potential_contexts() {
+        $this->resetAfterTest();
+
+        $this->setAdminUser();
+
+        // Include the all context levels so the misc. category get included.
+        $this->assertCount(1, \tool_analytics\external::potential_contexts());
+
+        // The frontpage is not included.
+        $this->assertCount(0, \tool_analytics\external::potential_contexts('PHPUnit'));
+
+        $target = \core_analytics\manager::get_target('test_target_course_level_shortname');
+        $indicators = ['test_indicator_max' => \core_analytics\manager::get_indicator('test_indicator_max')];
+        $model = \core_analytics\model::create($target, $indicators);
+
+        $this->assertCount(1, \tool_analytics\external::potential_contexts(null, $model->get_id()));
+    }
+
+    /**
+     * test_potential_contexts description
+     *
+     * @expectedException required_capability_exception
+     */
+    public function test_potential_contexts_no_manager() {
+        $this->resetAfterTest();
+
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+
+        $this->assertCount(2, \tool_analytics\external::potential_contexts());
+    }
+}
index 03c5187..c2e2b1c 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2019052000; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2019052002; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2019051100; // Requires this Moodle version.
 $plugin->component = 'tool_analytics'; // Full name of the plugin (used for diagnostics).
index 6ea0d22..99735bd 100644 (file)
@@ -448,10 +448,11 @@ abstract class base {
      * This generic implementation returns all the contexts in the site for the provided context level.
      * Overwrite it for specific restrictions in your analyser.
      *
+     * @param  string|null $query Context name filter.
      * @return int[]
      */
-    public static function potential_context_restrictions() {
-        return \core_analytics\manager::get_potential_context_restrictions(static::context_restriction_support());
+    public static function potential_context_restrictions(string $query = null) {
+        return \core_analytics\manager::get_potential_context_restrictions(static::context_restriction_support(), $query);
     }
 
     /**
index a36a1fe..131c782 100644 (file)
@@ -921,9 +921,10 @@ class manager {
      *
      * @throws \coding_exception
      * @param  array|null $contextlevels The list of context levels provided by the analyser. Null if all of them.
+     * @param  string|null $query
      * @return array Associative array with contextid as key and the short version of the context name as value.
      */
-    public static function get_potential_context_restrictions(?array $contextlevels = null) {
+    public static function get_potential_context_restrictions(?array $contextlevels = null, string $query = null) {
         global $DB;
 
         if (empty($contextlevels) && !is_null($contextlevels)) {
@@ -948,7 +949,14 @@ class manager {
             $sql = "SELECT cc.id, cc.name, ctx.id AS contextid
                       FROM {course_categories} cc
                       JOIN {context} ctx ON ctx.contextlevel = :ctxlevel AND ctx.instanceid = cc.id";
-            $coursecats = $DB->get_recordset_sql($sql, ['ctxlevel' => CONTEXT_COURSECAT]);
+            $params = ['ctxlevel' => CONTEXT_COURSECAT];
+
+            if ($query) {
+                $sql .= " WHERE " . $DB->sql_like('cc.name', ':query', false, false);
+                $params['query'] = '%' . $query . '%';
+            }
+
+            $coursecats = $DB->get_recordset_sql($sql, $params);
             foreach ($coursecats as $record) {
                 $contexts[$record->contextid] = get_string('category') . ': ' .
                     format_string($record->name, true, array('context' => $contextsystem));
@@ -960,8 +968,18 @@ class manager {
 
             $sql = "SELECT c.id, c.shortname, ctx.id AS contextid
                       FROM {course} c
-                      JOIN {context} ctx ON ctx.contextlevel = :ctxlevel AND ctx.instanceid = c.id";
-            $courses = $DB->get_recordset_sql($sql, ['ctxlevel' => CONTEXT_COURSE]);
+                      JOIN {context} ctx ON ctx.contextlevel = :ctxlevel AND ctx.instanceid = c.id
+                      WHERE c.id != :siteid";
+            $params = ['ctxlevel' => CONTEXT_COURSE, 'siteid' => SITEID];
+
+            if ($query) {
+                $sql .= ' AND (' . $DB->sql_like('c.fullname', ':query1', false, false) . ' OR ' .
+                    $DB->sql_like('c.shortname', ':query2', false, false) . ')';
+                $params['query1'] = '%' . $query . '%';
+                $params['query2'] = '%' . $query . '%';
+            }
+
+            $courses = $DB->get_recordset_sql($sql, $params);
             foreach ($courses as $record) {
                 $contexts[$record->contextid] = get_string('course') . ': ' .
                     format_string($record->shortname, true, array('context' => $contextsystem));
index 28d5067..582c457 100644 (file)
@@ -497,15 +497,21 @@ class analytics_manager_testcase extends advanced_testcase {
         // No potential context restrictions.
         $this->assertFalse(\core_analytics\manager::get_potential_context_restrictions([]));
 
-        // Include the all context levels so the frontpage and the misc. category get included.
-        $this->assertCount(2, \core_analytics\manager::get_potential_context_restrictions());
+        // Include the all context levels so the misc. category get included.
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions());
 
         $this->getDataGenerator()->create_course();
         $this->getDataGenerator()->create_category();
-        $this->assertCount(4, \core_analytics\manager::get_potential_context_restrictions());
-        $this->assertCount(4, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE, CONTEXT_COURSECAT]));
+        $this->assertCount(3, \core_analytics\manager::get_potential_context_restrictions());
+        $this->assertCount(3, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE, CONTEXT_COURSECAT]));
 
-        $this->assertCount(2, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE]));
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE]));
         $this->assertCount(2, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSECAT]));
+
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSECAT], 'Course category'));
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSECAT], 'Course category 1'));
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSECAT], 'Miscellaneous'));
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE], 'Test course 1'));
+        $this->assertCount(1, \core_analytics\manager::get_potential_context_restrictions([CONTEXT_COURSE], 'Test course'));
     }
 }