MDL-52828 competencies: Filter by activity

On the competency breakdown report, allow filter by competencies linked to an activity.
This commit is contained in:
Damyon Wiese 2019-03-21 12:38:19 +08:00
parent 9d4f4f0051
commit 27737ae666
14 changed files with 140 additions and 17 deletions

View 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)';

View 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}}

View File

@ -1 +1 @@
define(["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],function(a,b,c,d,e,f,g){var h=function(b,c){this._regionSelector=b,this._userCompetencySelector=c,a(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return h.prototype._handleClick=function(c){var f=a(c.target).closest(this._userCompetencySelector),g=a(f).data("competencyid"),h=a(f).data("courseid"),i=a(f).data("userid");e.debug("Clicked on cell: competencyId="+g+", courseId="+h+", userId="+i);var j=d.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:i,competencyid:g,courseid:h}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:i,competencyid:g,courseid:h}}]);a.when.apply(a,j).then(function(a){this._contextLoaded.bind(this)(a)}.bind(this))["catch"](b.exception)},h.prototype._contextLoaded=function(a){var d=this;a.displayuser=!0,f.render("tool_lp/user_competency_summary_in_course",a).done(function(a,e){c.get_string("usercompetencysummary","report_competency").done(function(b){new g(b,a,f.runTemplateJS.bind(f,e),d._refresh.bind(d),(!0))}).fail(b.exception)}).fail(b.exception)},h.prototype._refresh=function(){var c=a(this._regionSelector),e=c.data("courseid"),f=c.data("userid");d.call([{methodname:"report_competency_data_for_report",args:{courseid:e,userid:f},done:this._pageContextLoaded.bind(this),fail:b.exception}])},h.prototype._pageContextLoaded=function(a){var c=this;f.render("report_competency/report",a).done(function(a,b){f.replaceNode(c._regionSelector,a,b)}).fail(b.exception)},h.prototype._regionSelector=null,h.prototype._userCompetencySelector=null,h});
define(["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],function(a,b,c,d,e,f,g){var h=function(b,c){this._regionSelector=b,this._userCompetencySelector=c,a(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return h.prototype._handleClick=function(c){var f=a(c.target).closest(this._userCompetencySelector),g=a(f).data("competencyid"),h=a(f).data("courseid"),i=a(f).data("userid");e.debug("Clicked on cell: competencyId="+g+", courseId="+h+", userId="+i);var j=d.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:i,competencyid:g,courseid:h}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:i,competencyid:g,courseid:h}}]);a.when.apply(a,j).then(function(a){this._contextLoaded.bind(this)(a)}.bind(this))["catch"](b.exception)},h.prototype._contextLoaded=function(a){var d=this;a.displayuser=!0,f.render("tool_lp/user_competency_summary_in_course",a).done(function(a,e){c.get_string("usercompetencysummary","report_competency").done(function(b){new g(b,a,f.runTemplateJS.bind(f,e),d._refresh.bind(d),(!0))}).fail(b.exception)}).fail(b.exception)},h.prototype._refresh=function(){var c=a(this._regionSelector),e=c.data("courseid"),f=c.data("moduleid"),g=c.data("userid");d.call([{methodname:"report_competency_data_for_report",args:{courseid:e,userid:g,moduleid:f},done:this._pageContextLoaded.bind(this),fail:b.exception}])},h.prototype._pageContextLoaded=function(a){var c=this;f.render("report_competency/report",a).done(function(a,b){f.replaceNode(c._regionSelector,a,b)}).fail(b.exception)},h.prototype._regionSelector=null,h.prototype._userCompetencySelector=null,h});

View File

@ -1 +1 @@
define(["jquery"],function(a){var b=function(b,c,d,e){this._baseUrl=c,this._userId=d+"",this._courseId=e,a(b).on("change",this._userChanged.bind(this))};return b.prototype._userChanged=function(b){var c=a(b.target).val(),d="?user="+c+"&id="+this._courseId;document.location=this._baseUrl+d},b.prototype._userId=null,b.prototype._courseId=null,b.prototype._baseUrl=null,b});
define(["jquery"],function(a){var b=function(b,c,d,e,f,g){this._baseUrl=d,this._userId=e+"",this._courseId=f,this._moduleId=g,a(b).on("change",this._userChanged.bind(this)),a(c).on("change",this._moduleChanged.bind(this))};return b.prototype._userChanged=function(b){var c=a(b.target).val(),d="?user="+c+"&id="+this._courseId+"&mod="+this._moduleId;document.location=this._baseUrl+d},b.prototype._moduleChanged=function(b){var c=a(b.target).val(),d="?mod="+c+"&id="+this._courseId+"&user="+this._userId;document.location=this._baseUrl+d},b.prototype._userId=null,b.prototype._moduleId=null,b.prototype._courseId=null,b.prototype._baseUrl=null,b});

View 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
}]);

View 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. */

View 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);

View 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) {

View 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,7 @@ class user_course_navigation implements renderable, templatable {
$data = new stdClass();
$data->userid = $this->userid;
$data->courseid = $this->courseid;
$data->moduleid = $this->moduleid;
$data->baseurl = $this->baseurl;
$data->groupselector = '';
@ -107,6 +114,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;

View 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');

View 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.';

View 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);
}
}

View File

@ -1,4 +1,4 @@
<div data-region="competency-breakdown-report" data-courseid="{{course.id}}" data-userid="{{user.id}}">
<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">

View File

@ -1,4 +1,4 @@
<div class="float-right card">
<div class="float-right card p-x-1 p-b-1">
<p>{{{groupselector}}}</p>
<form class="user-competency-course-navigation">
{{#hasusers}}
@ -11,13 +11,27 @@
</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}}