Merge branch 'MDL-52828-master' of git://github.com/damyon/moodle
authorJun Pataleta <jun@moodle.com>
Fri, 3 May 2019 02:59:42 +0000 (10:59 +0800)
committerJun Pataleta <jun@moodle.com>
Fri, 3 May 2019 02:59:42 +0000 (10:59 +0800)
15 files changed:
admin/tool/lp/lang/en/tool_lp.php
admin/tool/lp/templates/user_competency_course_navigation.mustache
report/competency/amd/build/grading_popup.min.js
report/competency/amd/build/user_course_navigation.min.js
report/competency/amd/src/grading_popup.js
report/competency/amd/src/user_course_navigation.js
report/competency/classes/external.php
report/competency/classes/output/report.php
report/competency/classes/output/user_course_navigation.php
report/competency/index.php
report/competency/lang/en/report_competency.php
report/competency/lib.php
report/competency/templates/report.mustache
report/competency/templates/user_course_navigation.mustache
report/competency/tests/behat/breakdown_by_activity.feature [new file with mode: 0644]

index 156299c..a197960 100644 (file)
@@ -107,6 +107,7 @@ $string['editthisuserevidence'] = 'Edit this evidence';
 $string['edituserevidence'] = 'Edit evidence';
 $string['evidence'] = 'Evidence';
 $string['findcourses'] = 'Find courses';
+$string['filterbyactivity'] = 'Filter competencies by resource or activity';
 $string['frameworkcannotbedeleted'] = 'The competency framework \'{$a}\' cannot be deleted';
 $string['hidden'] = 'Hidden';
 $string['hiddenhint'] = '(hidden)';
index 9ffc29a..331c3e1 100644 (file)
@@ -35,7 +35,7 @@
 
     // No example context because the JS is connected to webservices
 }}
-<div class="float-sm-right card card-block">
+<div class="float-sm-right card card-block p-x-1 p-b-1">
 <p>{{{groupselector}}}</p>
 <form class="user-competency-course-navigation">
 {{#hasusers}}
index 89e0339..177b49c 100644 (file)
Binary files a/report/competency/amd/build/grading_popup.min.js and b/report/competency/amd/build/grading_popup.min.js differ
index fe73037..7cc3d3f 100644 (file)
Binary files a/report/competency/amd/build/user_course_navigation.min.js and b/report/competency/amd/build/user_course_navigation.min.js differ
index f53b123..99d93bb 100644 (file)
@@ -90,11 +90,12 @@ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'cor
     GradingPopup.prototype._refresh = function() {
         var region = $(this._regionSelector);
         var courseId = region.data('courseid');
+        var moduleId = region.data('moduleid');
         var userId = region.data('userid');
 
         ajax.call([{
             methodname: 'report_competency_data_for_report',
-            args: {courseid: courseId, userid: userId},
+            args: {courseid: courseId, userid: userId, moduleid: moduleId},
             done: this._pageContextLoaded.bind(this),
             fail: notification.exception
         }]);
index 5f0b226..ae2bbc5 100644 (file)
@@ -27,16 +27,20 @@ define(['jquery'], function($) {
      * UserCourseNavigation
      *
      * @param {String} userSelector The selector of the user element.
+     * @param {String} moduleSelector The selector of the module element.
      * @param {String} baseUrl The base url for the page (no params).
      * @param {Number} userId The course id
      * @param {Number} courseId The user id
+     * @param {Number} moduleId The activity module (filter)
      */
-    var UserCourseNavigation = function(userSelector, baseUrl, userId, courseId) {
+    var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) {
         this._baseUrl = baseUrl;
         this._userId = userId + '';
         this._courseId = courseId;
+        this._moduleId = moduleId;
 
         $(userSelector).on('change', this._userChanged.bind(this));
+        $(moduleSelector).on('change', this._moduleChanged.bind(this));
     };
 
     /**
@@ -47,12 +51,26 @@ define(['jquery'], function($) {
      */
     UserCourseNavigation.prototype._userChanged = function(e) {
         var newUserId = $(e.target).val();
-        var queryStr = '?user=' + newUserId + '&id=' + this._courseId;
+        var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId;
+        document.location = this._baseUrl + queryStr;
+    };
+
+    /**
+     * The module was changed in the select list.
+     *
+     * @method _moduleChanged
+     * @param {Event} e the event
+     */
+    UserCourseNavigation.prototype._moduleChanged = function(e) {
+        var newModuleId = $(e.target).val();
+        var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId;
         document.location = this._baseUrl + queryStr;
     };
 
     /** @type {Number} The id of the user. */
     UserCourseNavigation.prototype._userId = null;
+    /** @type {Number} The id of the module. */
+    UserCourseNavigation.prototype._moduleId = null;
     /** @type {Number} The id of the course. */
     UserCourseNavigation.prototype._courseId = null;
     /** @type {String} Plugin base url. */
index ed62b52..d432e3c 100644 (file)
@@ -62,9 +62,15 @@ class external extends external_api {
             'The user id',
             VALUE_REQUIRED
         );
+        $moduleid = new external_value(
+            PARAM_INT,
+            'The module id',
+            VALUE_REQUIRED
+        );
         $params = array(
             'courseid' => $courseid,
-            'userid' => $userid
+            'userid' => $userid,
+            'moduleid' => $moduleid,
         );
         return new external_function_parameters($params);
     }
@@ -74,16 +80,18 @@ class external extends external_api {
      *
      * @param int $courseid The course id
      * @param int $userid The user id
+     * @param int $moduleid The module id
      * @return \stdClass
      */
-    public static function data_for_report($courseid, $userid) {
+    public static function data_for_report($courseid, $userid, $moduleid) {
         global $PAGE;
 
         $params = self::validate_parameters(
             self::data_for_report_parameters(),
             array(
                 'courseid' => $courseid,
-                'userid' => $userid
+                'userid' => $userid,
+                'moduleid' => $moduleid
             )
         );
         $context = context_course::instance($params['courseid']);
@@ -92,7 +100,7 @@ class external extends external_api {
             throw new coding_exception('invaliduser');
         }
 
-        $renderable = new output\report($params['courseid'], $params['userid']);
+        $renderable = new output\report($params['courseid'], $params['userid'], $params['moduleid']);
         $renderer = $PAGE->get_renderer('report_competency');
 
         $data = $renderable->export_for_template($renderer);
index a5d02f6..d792605 100644 (file)
@@ -51,6 +51,8 @@ class report implements renderable, templatable {
     protected $context;
     /** @var int $courseid */
     protected $courseid;
+    /** @var int $moduleid */
+    protected $moduleid;
     /** @var array $competencies */
     protected $competencies;
 
@@ -59,10 +61,12 @@ class report implements renderable, templatable {
      *
      * @param int $courseid The course id
      * @param int $userid The user id
+     * @param int $moduleid The module id
      */
-    public function __construct($courseid, $userid) {
+    public function __construct($courseid, $userid, $moduleid) {
         $this->courseid = $courseid;
         $this->userid = $userid;
+        $this->moduleid = $moduleid;
         $this->context = context_course::instance($courseid);
     }
 
@@ -77,6 +81,10 @@ class report implements renderable, templatable {
 
         $data = new stdClass();
         $data->courseid = $this->courseid;
+        $data->moduleid = $this->moduleid;
+        if (empty($data->moduleid)) {
+            $data->moduleid = 0;
+        }
 
         $course = $DB->get_record('course', array('id' => $this->courseid));
         $coursecontext = context_course::instance($course->id);
@@ -93,6 +101,23 @@ class report implements renderable, templatable {
         $data->usercompetencies = array();
         $coursecompetencies = api::list_course_competencies($this->courseid);
         $usercompetencycourses = api::list_user_competencies_in_course($this->courseid, $user->id);
+        if ($this->moduleid > 0) {
+            $modulecompetencies = api::list_course_module_competencies_in_course_module($this->moduleid);
+            foreach ($usercompetencycourses as $ucid => $usercompetency) {
+                $found = false;
+                foreach ($modulecompetencies as $mcid => $modulecompetency) {
+                    if ($modulecompetency->get('competencyid') == $usercompetency->get('competencyid')) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if (!$found) {
+                    // We need to filter out this competency.
+                    unset($usercompetencycourses[$ucid]);
+                }
+            }
+        }
 
         $helper = new performance_helper();
         foreach ($usercompetencycourses as $usercompetencycourse) {
index b5ad9e3..5773578 100644 (file)
@@ -28,6 +28,7 @@ use renderer_base;
 use templatable;
 use context_course;
 use core_user\external\user_summary_exporter;
+use core_course\external\course_module_summary_exporter;
 use stdClass;
 
 /**
@@ -45,6 +46,9 @@ class user_course_navigation implements renderable, templatable {
     /** @var courseid */
     protected $courseid;
 
+    /** @var moduleid */
+    protected $moduleid;
+
     /** @var baseurl */
     protected $baseurl;
 
@@ -53,11 +57,13 @@ class user_course_navigation implements renderable, templatable {
      *
      * @param int $userid
      * @param int $courseid
+     * @param int $moduleid
      * @param string $baseurl
      */
-    public function __construct($userid, $courseid, $baseurl) {
+    public function __construct($userid, $courseid, $baseurl, $moduleid) {
         $this->userid = $userid;
         $this->courseid = $courseid;
+        $this->moduleid = $moduleid;
         $this->baseurl = $baseurl;
     }
 
@@ -75,6 +81,11 @@ class user_course_navigation implements renderable, templatable {
         $data = new stdClass();
         $data->userid = $this->userid;
         $data->courseid = $this->courseid;
+        $data->moduleid = $this->moduleid;
+        if (empty($data->moduleid)) {
+            // Moduleid is optional.
+            $data->moduleid = 0;
+        }
         $data->baseurl = $this->baseurl;
         $data->groupselector = '';
 
@@ -107,6 +118,25 @@ class user_course_navigation implements renderable, templatable {
                 $data->users[] = $user;
             }
             $data->hasusers = true;
+
+            $data->hasmodules = true;
+            $data->modules = array();
+            $empty = (object)['id' => 0, 'name' => get_string('nofiltersapplied')];
+            $data->modules[] = $empty;
+
+            $modinfo = get_fast_modinfo($this->courseid);
+            foreach ($modinfo->get_cms() as $cm) {
+                if ($cm->uservisible) {
+                    $exporter = new course_module_summary_exporter(null, ['cm' => $cm]);
+                    $module = $exporter->export($output);
+                    if ($module->id == $this->moduleid) {
+                        $module->selected = true;
+                    }
+                    $data->modules[] = $module;
+                    $data->hasmodules = true;
+                }
+            }
+
         } else {
             $data->users = array();
             $data->hasusers = false;
index 134518a..6c1fc25 100644 (file)
@@ -31,6 +31,11 @@ $course = $DB->get_record('course', $params, '*', MUST_EXIST);
 require_login($course);
 $context = context_course::instance($course->id);
 $currentuser = optional_param('user', null, PARAM_INT);
+$currentmodule = optional_param('mod', null, PARAM_INT);
+if ($currentmodule > 0) {
+    $cm = get_coursemodule_from_id('', $currentmodule, 0, false, MUST_EXIST);
+    $context = context_module::instance($cm->id);
+}
 
 // Fetch current active group.
 $groupmode = groups_get_course_groupmode($course);
@@ -54,6 +59,7 @@ if (empty($currentuser)) {
 $urlparams = array('id' => $id);
 $navurl = new moodle_url('/report/competency/index.php', $urlparams);
 $urlparams['user'] = $currentuser;
+$urlparams['mod'] = $currentmodule;
 $url = new moodle_url('/report/competency/index.php', $urlparams);
 
 $title = get_string('pluginname', 'report_competency');
@@ -69,7 +75,7 @@ $output = $PAGE->get_renderer('report_competency');
 
 echo $output->header();
 $baseurl = new moodle_url('/report/competency/index.php');
-$nav = new \report_competency\output\user_course_navigation($currentuser, $course->id, $baseurl);
+$nav = new \report_competency\output\user_course_navigation($currentuser, $course->id, $baseurl, $currentmodule);
 echo $output->render($nav);
 if ($currentuser > 0) {
     $user = core_user::get_user($currentuser);
@@ -79,12 +85,16 @@ if ($currentuser > 0) {
         'user' => $user,
         'usercontext' => $usercontext
     );
+    if ($currentmodule > 0) {
+        $title = get_string('filtermodule', 'report_competency', format_string($cm->name));
+    }
     echo $output->context_header($userheading, 3);
 }
+echo $output->container('', 'clearfix');
 echo $output->heading($title, 3);
 
 if ($currentuser > 0) {
-    $page = new \report_competency\output\report($course->id, $currentuser);
+    $page = new \report_competency\output\report($course->id, $currentuser, $currentmodule);
     echo $output->render($page);
 } else {
     echo $output->container('', 'clearfix');
index a49ae5f..c826e41 100644 (file)
@@ -27,5 +27,6 @@ $string['coursecompetencybreakdownsummary'] = 'A report of all the students in t
 $string['notrated'] = 'Not rated';
 $string['pluginname'] = 'Competency breakdown';
 $string['rating'] = 'Rating';
+$string['filtermodule'] = 'Competencies linked to "{$a}"';
 $string['usercompetencysummary'] = 'User competency summary';
 $string['privacy:metadata'] = 'The Competency breakdown plugin does not store any personal data.';
index 5476c55..497c91b 100644 (file)
@@ -44,3 +44,22 @@ function report_competency_extend_navigation_course($navigation, $course, $conte
         $navigation->add($name, $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
 }
+
+/**
+ * This function extends the navigation with the report items
+ *
+ * @param navigation_node $navigation The navigation node to extend
+ * @param cminfo $cm The course module.
+ */
+function report_competency_extend_navigation_module($navigation, $cm) {
+    if (!get_config('core_competency', 'enabled')) {
+        return;
+    }
+
+    if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
+            context_course::instance($cm->course))) {
+        $url = new moodle_url('/report/competency/index.php', array('id' => $cm->course, 'mod' => $cm->id));
+        $name = get_string('pluginname', 'report_competency');
+        $navigation->add($name, $url, navigation_node::TYPE_SETTING, null, null);
+    }
+}
index 09b44dc..24622d7 100644 (file)
@@ -1,4 +1,28 @@
-<div data-region="competency-breakdown-report" data-courseid="{{course.id}}" data-userid="{{user.id}}">
+{{!
+    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/>.
+}}
+{{!
+    @template report_competency/report
+
+    Moodle template competency breakdown report.
+
+    Example context (json):
+    { "pushratingstouserplans": false, "usercompetencies": []}
+}}
+<div data-region="competency-breakdown-report" data-courseid="{{course.id}}" data-userid="{{user.id}}" data-moduleid="{{moduleid}}">
 <div data-region="configurecoursecompetencies">
 {{#pushratingstouserplans}}
     <p class="alert">
index fd15d48..ad430a6 100644 (file)
@@ -1,4 +1,28 @@
-<div class="float-right card">
+{{!
+    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/>.
+}}
+{{!
+    @template report_competency/user_course_navigation
+
+    Moodle navigation control allowing to jump to a user or filter to an activity.
+
+    Example context (json):
+    { "hasusers": false, "hasmodules": false}
+}}
+<div class="float-right card p-x-1 p-b-1">
 <p>{{{groupselector}}}</p>
 <form class="user-competency-course-navigation">
 {{#hasusers}}
 </select>
 </span>
 {{/hasusers}}
+
+{{#hasmodules}}
+<span>
+<label for="module-nav-{{uniqid}}" class="accesshide">{{#str}}filterbyactivity, tool_lp{{/str}}</label>
+<select id="module-nav-{{uniqid}}">
+{{#modules}}
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{name}}</option>
+{{/modules}}
+</select>
+</span>
+{{/hasmodules}}
 </form>
 </div>
 {{#js}}
 require(['core/form-autocomplete', 'report_competency/user_course_navigation'], function(autocomplete, nav) {
-    (new nav('#user-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{courseid}}));
+    (new nav('#user-nav-{{uniqid}}', '#module-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{courseid}}, {{moduleid}}));
 {{#hasusers}}
     autocomplete.enhance('#user-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptouser, tool_lp{{/ str }}{{/ quote }});
 {{/hasusers}}
+{{#hasmodules}}
+    autocomplete.enhance('#module-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}filterbyactivity, tool_lp{{/ str }}{{/ quote }});
+{{/hasmodules}}
 });
 {{/js}}
diff --git a/report/competency/tests/behat/breakdown_by_activity.feature b/report/competency/tests/behat/breakdown_by_activity.feature
new file mode 100644 (file)
index 0000000..dfa0876
--- /dev/null
@@ -0,0 +1,61 @@
+@report @javascript @report_competency
+Feature: See the competencies for an activity
+  As a competency grader
+  In order to perform mark all competencies for an activity
+  I need to see the competencies linked to one activity in the breakdown report.
+
+  Background:
+    Given the following lp "frameworks" exist:
+      | shortname | idnumber |
+      | Test-Framework | ID-FW1 |
+    And the following lp "competencies" exist:
+      | shortname | framework |
+      | Test-Comp1 | ID-FW1 |
+      | Test-Comp2 | ID-FW1 |
+    Given the following "courses" exist:
+      | shortname | fullname   |
+      | C1        | Course 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | student1 | C1 | student |
+    And the following "activities" exist:
+      | activity | name       | intro      | course | idnumber |
+      | page     | PageName1  | PageDesc1  | C1     | PAGE1    |
+    And I log in as "admin"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I follow "Competencies"
+    And I press "Add competencies to course"
+    And "Competency picker" "dialogue" should be visible
+    And I select "Test-Comp1" of the competency tree
+    And I click on "Add" "button" in the "Competency picker" "dialogue"
+    And I press "Add competencies to course"
+    And "Competency picker" "dialogue" should be visible
+    And I select "Test-Comp2" of the competency tree
+    And I click on "Add" "button" in the "Competency picker" "dialogue"
+    And I am on "Course 1" course homepage
+    And I follow "PageName1"
+    And I navigate to "Edit settings" in current page administration
+    And I follow "Expand all"
+    And I set the field "Course competencies" to "Test-Comp1"
+    And I press "Save and return to course"
+
+  @javascript
+  Scenario: Go to the competency breakdown report
+    When I navigate to "Reports > Competency breakdown" in current page administration
+    And I set the field "Filter competencies by resource or activity" to "PageName1"
+    And I press key "13" in the field "Filter competencies by resource or activity"
+    Then I should see "Test-Comp1"
+    And I should not see "Test-Comp2"
+    And I click on "Not rated" "link"
+    And I click on "Rate" "button"
+    And I set the field "Rating" to "A"
+    And I click on "Rate" "button" in the ".competency-grader" "css_element"
+    And I click on "Close" "button"
+    And I set the field "Filter competencies by resource or activity" to "No filters applied"
+    And I press key "13" in the field "Filter competencies by resource or activity"
+    And I should see "Test-Comp1"
+    And I should see "Test-Comp2"