mirror of
https://github.com/moodle/moodle.git
synced 2025-08-09 10:56:56 +02:00
MDL-80744 mod_assign: Add user search.
This commit is contained in:
parent
8087893fcb
commit
de54a993d3
16 changed files with 656 additions and 97 deletions
|
@ -117,7 +117,13 @@ class action_bar extends \core_grades\output\action_bar {
|
||||||
}
|
}
|
||||||
|
|
||||||
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);
|
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);
|
||||||
$userselectorrenderer = new \core_course\output\actionbar\user_selector($course, $resetlink, $this->userid, 0, $this->usersearch);
|
$userselectorrenderer = new \core_course\output\actionbar\user_selector(
|
||||||
|
course: $course,
|
||||||
|
resetlink: $resetlink,
|
||||||
|
userid: $this->userid,
|
||||||
|
groupid: 0,
|
||||||
|
usersearch: $this->usersearch
|
||||||
|
);
|
||||||
$data['searchdropdown'] = $userselectorrenderer->export_for_template($output);
|
$data['searchdropdown'] = $userselectorrenderer->export_for_template($output);
|
||||||
// The collapsed column dialog is aligned to the edge of the screen, we need to place it such that it also aligns.
|
// The collapsed column dialog is aligned to the edge of the screen, we need to place it such that it also aligns.
|
||||||
$collapsemenudirection = right_to_left() ? 'dropdown-menu-left' : 'dropdown-menu-right';
|
$collapsemenudirection = right_to_left() ? 'dropdown-menu-left' : 'dropdown-menu-right';
|
||||||
|
|
|
@ -485,108 +485,15 @@ abstract class grade_report {
|
||||||
|
|
||||||
// A user wants to return a subset of learners that match their search criteria.
|
// A user wants to return a subset of learners that match their search criteria.
|
||||||
if ($this->usersearch !== '' && $this->userid === -1) {
|
if ($this->usersearch !== '' && $this->userid === -1) {
|
||||||
// Get the fields for all contexts because there is a special case later where it allows
|
|
||||||
// matches of fields you can't access if they are on your own account.
|
|
||||||
$userfields = fields::for_identity(null, false)->with_userpic();
|
|
||||||
['mappings' => $mappings] = (array)$userfields->get_sql('u', true);
|
|
||||||
[
|
[
|
||||||
'where' => $keywordswhere,
|
'where' => $keywordswhere,
|
||||||
'params' => $keywordsparams,
|
'params' => $keywordsparams,
|
||||||
] = $this->get_users_search_sql($mappings, $userfields->get_required_fields());
|
] = \core_user::get_users_search_sql($this->context, $this->usersearch);
|
||||||
$this->userwheresql .= " AND $keywordswhere";
|
$this->userwheresql .= " AND $keywordswhere";
|
||||||
$this->userwheresql_params = array_merge($this->userwheresql_params, $keywordsparams);
|
$this->userwheresql_params = array_merge($this->userwheresql_params, $keywordsparams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare SQL where clause and associated parameters for any user searching being performed.
|
|
||||||
* This mostly came from core_user\table\participants_search with some slight modifications four our use case.
|
|
||||||
*
|
|
||||||
* @param array $mappings Array of field mappings (fieldname => SQL code for the value)
|
|
||||||
* @param array $userfields An array that we cast from user profile fields to search within.
|
|
||||||
* @return array SQL query data in the format ['where' => '', 'params' => []].
|
|
||||||
*/
|
|
||||||
protected function get_users_search_sql(array $mappings, array $userfields): array {
|
|
||||||
global $DB, $USER;
|
|
||||||
|
|
||||||
$canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
|
|
||||||
|
|
||||||
$params = [];
|
|
||||||
$searchkey1 = 'search01';
|
|
||||||
$searchkey2 = 'search02';
|
|
||||||
$searchkey3 = 'search03';
|
|
||||||
|
|
||||||
$conditions = [];
|
|
||||||
|
|
||||||
// Search by fullname.
|
|
||||||
[$fullname, $fullnameparams] = fields::get_sql_fullname('u', $canviewfullnames);
|
|
||||||
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
|
|
||||||
$params = array_merge($params, $fullnameparams);
|
|
||||||
|
|
||||||
// Search by email.
|
|
||||||
$email = $DB->sql_like('email', ':' . $searchkey2, false, false);
|
|
||||||
|
|
||||||
if (!in_array('email', $userfields)) {
|
|
||||||
$maildisplay = 'maildisplay0';
|
|
||||||
$userid1 = 'userid01';
|
|
||||||
// Prevent users who hide their email address from being found by others
|
|
||||||
// who aren't allowed to see hidden email addresses.
|
|
||||||
$email = "(". $email ." AND (" .
|
|
||||||
"u.maildisplay <> :$maildisplay " .
|
|
||||||
"OR u.id = :$userid1". // Users can always find themselves.
|
|
||||||
"))";
|
|
||||||
$params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
|
|
||||||
$params[$userid1] = $USER->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$conditions[] = $email;
|
|
||||||
|
|
||||||
// Search by idnumber.
|
|
||||||
$idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
|
|
||||||
|
|
||||||
if (!in_array('idnumber', $userfields)) {
|
|
||||||
$userid2 = 'userid02';
|
|
||||||
// Users who aren't allowed to see idnumbers should at most find themselves
|
|
||||||
// when searching for an idnumber.
|
|
||||||
$idnumber = "(". $idnumber . " AND u.id = :$userid2)";
|
|
||||||
$params[$userid2] = $USER->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
$conditions[] = $idnumber;
|
|
||||||
|
|
||||||
// Search all user identify fields.
|
|
||||||
$extrasearchfields = fields::get_identity_fields(null, false);
|
|
||||||
foreach ($extrasearchfields as $fieldindex => $extrasearchfield) {
|
|
||||||
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
|
|
||||||
// Already covered above.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// The param must be short (max 32 characters) so don't include field name.
|
|
||||||
$param = $searchkey3 . '_ident' . $fieldindex;
|
|
||||||
$fieldsql = $mappings[$extrasearchfield];
|
|
||||||
$condition = $DB->sql_like($fieldsql, ':' . $param, false, false);
|
|
||||||
$params[$param] = "%$this->usersearch%";
|
|
||||||
|
|
||||||
if (!in_array($extrasearchfield, $userfields)) {
|
|
||||||
// User cannot see this field, but allow match if their own account.
|
|
||||||
$userid3 = 'userid03_ident' . $fieldindex;
|
|
||||||
$condition = "(". $condition . " AND u.id = :$userid3)";
|
|
||||||
$params[$userid3] = $USER->id;
|
|
||||||
}
|
|
||||||
$conditions[] = $condition;
|
|
||||||
}
|
|
||||||
|
|
||||||
$where = "(". implode(" OR ", $conditions) .") ";
|
|
||||||
$params[$searchkey1] = "%$this->usersearch%";
|
|
||||||
$params[$searchkey2] = "%$this->usersearch%";
|
|
||||||
$params[$searchkey3] = "%$this->usersearch%";
|
|
||||||
|
|
||||||
return [
|
|
||||||
'where' => $where,
|
|
||||||
'params' => $params,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an arrow icon inside an <a> tag, for the purpose of sorting a column.
|
* Returns an arrow icon inside an <a> tag, for the purpose of sorting a column.
|
||||||
* @param string $direction
|
* @param string $direction
|
||||||
|
|
|
@ -99,7 +99,12 @@ class action_bar extends \core_grades\output\action_bar {
|
||||||
}
|
}
|
||||||
$data['userselector'] = [
|
$data['userselector'] = [
|
||||||
'courseid' => $courseid,
|
'courseid' => $courseid,
|
||||||
'content' => $userreportrenderer->users_selector(get_course($courseid), $this->userid, $this->currentgroupid, $this->usersearch)
|
'content' => $userreportrenderer->users_selector(
|
||||||
|
course: get_course($courseid),
|
||||||
|
userid: $this->userid,
|
||||||
|
groupid: $this->currentgroupid,
|
||||||
|
usersearch: $this->usersearch
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Do not output the 'view mode' selector when in zero state or when the current user is viewing its own report.
|
// Do not output the 'view mode' selector when in zero state or when the current user is viewing its own report.
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use core_user\fields;
|
||||||
|
|
||||||
defined('MOODLE_INTERNAL') || die();
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1489,4 +1491,97 @@ class core_user {
|
||||||
return $initials;
|
return $initials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare SQL where clause and associated parameters for any user searching being performed.
|
||||||
|
* This mostly came from core_user\table\participants_search with some slight modifications four our use case.
|
||||||
|
*
|
||||||
|
* @param context $context Context we are in.
|
||||||
|
* @param string $usersearch Array of field mappings (fieldname => SQL code for the value)
|
||||||
|
* @return array SQL query data in the format ['where' => '', 'params' => []].
|
||||||
|
*/
|
||||||
|
public static function get_users_search_sql(context $context, string $usersearch = ''): array {
|
||||||
|
global $DB, $USER;
|
||||||
|
|
||||||
|
$userfields = fields::for_identity($context, false)->with_userpic();
|
||||||
|
['mappings' => $mappings] = (array)$userfields->get_sql('u', true);
|
||||||
|
$userfields = $userfields->get_required_fields();
|
||||||
|
|
||||||
|
$canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
$searchkey1 = 'search01';
|
||||||
|
$searchkey2 = 'search02';
|
||||||
|
$searchkey3 = 'search03';
|
||||||
|
|
||||||
|
$conditions = [];
|
||||||
|
|
||||||
|
// Search by fullname.
|
||||||
|
[$fullname, $fullnameparams] = fields::get_sql_fullname('u', $canviewfullnames);
|
||||||
|
$conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
|
||||||
|
$params = array_merge($params, $fullnameparams);
|
||||||
|
|
||||||
|
// Search by email.
|
||||||
|
$email = $DB->sql_like('email', ':' . $searchkey2, false, false);
|
||||||
|
|
||||||
|
if (!in_array('email', $userfields)) {
|
||||||
|
$maildisplay = 'maildisplay0';
|
||||||
|
$userid1 = 'userid01';
|
||||||
|
// Prevent users who hide their email address from being found by others
|
||||||
|
// who aren't allowed to see hidden email addresses.
|
||||||
|
$email = "(". $email ." AND (" .
|
||||||
|
"u.maildisplay <> :$maildisplay " .
|
||||||
|
"OR u.id = :$userid1". // Users can always find themselves.
|
||||||
|
"))";
|
||||||
|
$params[$maildisplay] = self::MAILDISPLAY_HIDE;
|
||||||
|
$params[$userid1] = $USER->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions[] = $email;
|
||||||
|
|
||||||
|
// Search by idnumber.
|
||||||
|
$idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
|
||||||
|
|
||||||
|
if (!in_array('idnumber', $userfields)) {
|
||||||
|
$userid2 = 'userid02';
|
||||||
|
// Users who aren't allowed to see idnumbers should at most find themselves
|
||||||
|
// when searching for an idnumber.
|
||||||
|
$idnumber = "(". $idnumber . " AND u.id = :$userid2)";
|
||||||
|
$params[$userid2] = $USER->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions[] = $idnumber;
|
||||||
|
|
||||||
|
// Search all user identify fields.
|
||||||
|
$extrasearchfields = fields::get_identity_fields(null, false);
|
||||||
|
foreach ($extrasearchfields as $fieldindex => $extrasearchfield) {
|
||||||
|
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
|
||||||
|
// Already covered above.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The param must be short (max 32 characters) so don't include field name.
|
||||||
|
$param = $searchkey3 . '_ident' . $fieldindex;
|
||||||
|
$fieldsql = $mappings[$extrasearchfield];
|
||||||
|
$condition = $DB->sql_like($fieldsql, ':' . $param, false, false);
|
||||||
|
$params[$param] = "%$usersearch%";
|
||||||
|
|
||||||
|
if (!in_array($extrasearchfield, $userfields)) {
|
||||||
|
// User cannot see this field, but allow match if their own account.
|
||||||
|
$userid3 = 'userid03_ident' . $fieldindex;
|
||||||
|
$condition = "(". $condition . " AND u.id = :$userid3)";
|
||||||
|
$params[$userid3] = $USER->id;
|
||||||
|
}
|
||||||
|
$conditions[] = $condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = "(". implode(" OR ", $conditions) .") ";
|
||||||
|
$params[$searchkey1] = "%$usersearch%";
|
||||||
|
$params[$searchkey2] = "%$usersearch%";
|
||||||
|
$params[$searchkey3] = "%$usersearch%";
|
||||||
|
|
||||||
|
return [
|
||||||
|
'where' => $where,
|
||||||
|
'params' => $params,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
10
mod/assign/amd/build/repository.min.js
vendored
Normal file
10
mod/assign/amd/build/repository.min.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
define("mod_assign/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
|
||||||
|
/**
|
||||||
|
* A repo for the search partial in the submissions page.
|
||||||
|
*
|
||||||
|
* @module mod_assign/repository
|
||||||
|
* @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.userFetch=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.userFetch=(assignid,groupid)=>{const request={methodname:"mod_assign_list_participants",args:{assignid:assignid,groupid:groupid,filter:""}};return _ajax.default.call([request])[0]}}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=repository.min.js.map
|
1
mod/assign/amd/build/repository.min.js.map
Normal file
1
mod/assign/amd/build/repository.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A repo for the search partial in the submissions page.\n *\n * @module mod_assign/repository\n * @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from 'core/ajax';\n\n/**\n * Given a course ID, we want to fetch the learners within this assignment.\n *\n * @method userFetch\n * @param {int} assignid ID of the assignment.\n * @param {int} groupid ID of the selected group.\n * @return {object} jQuery promise\n */\nexport const userFetch = (assignid, groupid) => {\n const request = {\n methodname: 'mod_assign_list_participants',\n args: {\n assignid: assignid,\n groupid: groupid,\n filter: '',\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["assignid","groupid","request","methodname","args","filter","ajax","call"],"mappings":";;;;;;;8JAiCyB,CAACA,SAAUC,iBAC1BC,QAAU,CACZC,WAAY,+BACZC,KAAM,CACFJ,SAAUA,SACVC,QAASA,QACTI,OAAQ,YAGTC,cAAKC,KAAK,CAACL,UAAU"}
|
11
mod/assign/amd/build/user.min.js
vendored
Normal file
11
mod/assign/amd/build/user.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
define("mod_assign/user",["exports","core_user/comboboxsearch/user","mod_assign/repository"],(function(_exports,_user,Repository){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_user=(obj=_user)&&obj.__esModule?obj:{default:obj},Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Repository);const selectors_component=".user-search",selectors_groupid='[data-region="groupid"]',selectors_instance='[data-region="instance"]',component=document.querySelector(selectors_component),groupID=parseInt(component.querySelector(selectors_groupid).dataset.groupid,10),assignID=parseInt(component.querySelector(selectors_instance).dataset.instance,10);
|
||||||
|
/**
|
||||||
|
* Allow the user to search for users in the action bar.
|
||||||
|
*
|
||||||
|
* @module mod_assign/user
|
||||||
|
* @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class User extends _user.default{constructor(baseUrl){super(),this.baseUrl=baseUrl}static init(baseUrl){return new User(baseUrl)}selectAllResultsLink(){const url=new URL(this.baseUrl);return url.searchParams.set("search",this.getSearchTerm()),url.toString()}fetchDataset(){return Repository.userFetch(assignID,groupID).then((r=>r))}selectOneLink(userID){const url=new URL(this.baseUrl);return url.searchParams.set("search",this.getSearchTerm()),url.searchParams.set("userid",userID.toString()),url.toString()}}return _exports.default=User,_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=user.min.js.map
|
1
mod/assign/amd/build/user.min.js.map
Normal file
1
mod/assign/amd/build/user.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"user.min.js","sources":["../src/user.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\nimport UserSearch from 'core_user/comboboxsearch/user';\nimport * as Repository from 'mod_assign/repository';\n\n// Define our standard lookups.\nconst selectors = {\n component: '.user-search',\n groupid: '[data-region=\"groupid\"]',\n instance: '[data-region=\"instance\"]',\n currentvalue: '[data-region=\"currentvalue\"]',\n};\nconst component = document.querySelector(selectors.component);\nconst groupID = parseInt(component.querySelector(selectors.groupid).dataset.groupid, 10);\nconst assignID = parseInt(component.querySelector(selectors.instance).dataset.instance, 10);\n\n/**\n * Allow the user to search for users in the action bar.\n *\n * @module mod_assign/user\n * @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class User extends UserSearch {\n\n /**\n * Construct the class.\n *\n * @param {string} baseUrl The base URL for the page.\n */\n constructor(baseUrl) {\n super();\n this.baseUrl = baseUrl;\n }\n\n /**\n * Allow the class to be invoked via PHP.\n *\n * @param {string} baseUrl The base URL for the page.\n * @returns {User}\n */\n static init(baseUrl) {\n return new User(baseUrl);\n }\n\n /**\n * Build up the view all link.\n *\n * @returns {string|*}\n */\n selectAllResultsLink() {\n const url = new URL(this.baseUrl);\n url.searchParams.set('search', this.getSearchTerm());\n\n return url.toString();\n }\n\n /**\n * Get the data we will be searching against in this component.\n *\n * @returns {Promise<*>}\n */\n fetchDataset() {\n return Repository.userFetch(assignID, groupID).then((r) => r);\n }\n\n /**\n * Build up the link that is dedicated to a particular result.\n *\n * @param {Number} userID The ID of the user selected.\n * @returns {string|*}\n */\n selectOneLink(userID) {\n const url = new URL(this.baseUrl);\n url.searchParams.set('search', this.getSearchTerm());\n url.searchParams.set('userid', userID.toString());\n\n return url.toString();\n }\n}\n"],"names":["selectors","component","document","querySelector","groupID","parseInt","dataset","groupid","assignID","instance","User","UserSearch","constructor","baseUrl","selectAllResultsLink","url","URL","this","searchParams","set","getSearchTerm","toString","fetchDataset","Repository","userFetch","then","r","selectOneLink","userID"],"mappings":"2sCAmBMA,oBACS,eADTA,kBAEO,0BAFPA,mBAGQ,2BAGRC,UAAYC,SAASC,cAAcH,qBACnCI,QAAUC,SAASJ,UAAUE,cAAcH,mBAAmBM,QAAQC,QAAS,IAC/EC,SAAWH,SAASJ,UAAUE,cAAcH,oBAAoBM,QAAQG,SAAU;;;;;;;;MASnEC,aAAaC,cAO9BC,YAAYC,sBAEHA,QAAUA,oBASPA,gBACD,IAAIH,KAAKG,SAQpBC,6BACUC,IAAM,IAAIC,IAAIC,KAAKJ,gBACzBE,IAAIG,aAAaC,IAAI,SAAUF,KAAKG,iBAE7BL,IAAIM,WAQfC,sBACWC,WAAWC,UAAUhB,SAAUJ,SAASqB,MAAMC,GAAMA,IAS/DC,cAAcC,cACJb,IAAM,IAAIC,IAAIC,KAAKJ,gBACzBE,IAAIG,aAAaC,IAAI,SAAUF,KAAKG,iBACpCL,IAAIG,aAAaC,IAAI,SAAUS,OAAOP,YAE/BN,IAAIM"}
|
44
mod/assign/amd/src/repository.js
Normal file
44
mod/assign/amd/src/repository.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repo for the search partial in the submissions page.
|
||||||
|
*
|
||||||
|
* @module mod_assign/repository
|
||||||
|
* @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ajax from 'core/ajax';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a course ID, we want to fetch the learners within this assignment.
|
||||||
|
*
|
||||||
|
* @method userFetch
|
||||||
|
* @param {int} assignid ID of the assignment.
|
||||||
|
* @param {int} groupid ID of the selected group.
|
||||||
|
* @return {object} jQuery promise
|
||||||
|
*/
|
||||||
|
export const userFetch = (assignid, groupid) => {
|
||||||
|
const request = {
|
||||||
|
methodname: 'mod_assign_list_participants',
|
||||||
|
args: {
|
||||||
|
assignid: assignid,
|
||||||
|
groupid: groupid,
|
||||||
|
filter: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return ajax.call([request])[0];
|
||||||
|
};
|
93
mod/assign/amd/src/user.js
Normal file
93
mod/assign/amd/src/user.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// 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 UserSearch from 'core_user/comboboxsearch/user';
|
||||||
|
import * as Repository from 'mod_assign/repository';
|
||||||
|
|
||||||
|
// Define our standard lookups.
|
||||||
|
const selectors = {
|
||||||
|
component: '.user-search',
|
||||||
|
groupid: '[data-region="groupid"]',
|
||||||
|
instance: '[data-region="instance"]',
|
||||||
|
currentvalue: '[data-region="currentvalue"]',
|
||||||
|
};
|
||||||
|
const component = document.querySelector(selectors.component);
|
||||||
|
const groupID = parseInt(component.querySelector(selectors.groupid).dataset.groupid, 10);
|
||||||
|
const assignID = parseInt(component.querySelector(selectors.instance).dataset.instance, 10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow the user to search for users in the action bar.
|
||||||
|
*
|
||||||
|
* @module mod_assign/user
|
||||||
|
* @copyright 2024 Ilya Tregubov <ilyatregubov@proton.me>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
export default class User extends UserSearch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the class.
|
||||||
|
*
|
||||||
|
* @param {string} baseUrl The base URL for the page.
|
||||||
|
*/
|
||||||
|
constructor(baseUrl) {
|
||||||
|
super();
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow the class to be invoked via PHP.
|
||||||
|
*
|
||||||
|
* @param {string} baseUrl The base URL for the page.
|
||||||
|
* @returns {User}
|
||||||
|
*/
|
||||||
|
static init(baseUrl) {
|
||||||
|
return new User(baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build up the view all link.
|
||||||
|
*
|
||||||
|
* @returns {string|*}
|
||||||
|
*/
|
||||||
|
selectAllResultsLink() {
|
||||||
|
const url = new URL(this.baseUrl);
|
||||||
|
url.searchParams.set('search', this.getSearchTerm());
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data we will be searching against in this component.
|
||||||
|
*
|
||||||
|
* @returns {Promise<*>}
|
||||||
|
*/
|
||||||
|
fetchDataset() {
|
||||||
|
return Repository.userFetch(assignID, groupID).then((r) => r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build up the link that is dedicated to a particular result.
|
||||||
|
*
|
||||||
|
* @param {Number} userID The ID of the user selected.
|
||||||
|
* @returns {string|*}
|
||||||
|
*/
|
||||||
|
selectOneLink(userID) {
|
||||||
|
const url = new URL(this.baseUrl);
|
||||||
|
url.searchParams.set('search', this.getSearchTerm());
|
||||||
|
url.searchParams.set('userid', userID.toString());
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@
|
||||||
|
|
||||||
namespace mod_assign\output;
|
namespace mod_assign\output;
|
||||||
|
|
||||||
|
use assign;
|
||||||
|
use context_module;
|
||||||
use templatable;
|
use templatable;
|
||||||
use renderable;
|
use renderable;
|
||||||
use moodle_url;
|
use moodle_url;
|
||||||
|
@ -71,14 +73,35 @@ class grading_actionmenu implements templatable, renderable {
|
||||||
$course = $PAGE->course;
|
$course = $PAGE->course;
|
||||||
$data = [];
|
$data = [];
|
||||||
|
|
||||||
|
$context = context_module::instance($this->cmid);
|
||||||
|
$assign = new assign($context, null, null);
|
||||||
|
$assignid = $assign->get_instance()->id;
|
||||||
|
|
||||||
if ($this->submissionpluginenabled && $this->submissioncount) {
|
if ($this->submissionpluginenabled && $this->submissioncount) {
|
||||||
$data['downloadall'] = (
|
$data['downloadall'] = (
|
||||||
new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'downloadall'])
|
new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'downloadall'])
|
||||||
)->out(false);
|
)->out(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userid = optional_param('userid', null, PARAM_INT);
|
||||||
|
// If the user ID is set, it indicates that a user has been selected. In this case, override the user search
|
||||||
|
// string with the full name of the selected user.
|
||||||
|
$usersearch = $userid ? fullname(\core_user::get_user($userid)) : optional_param('search', '', PARAM_NOTAGS);
|
||||||
|
|
||||||
|
$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
|
||||||
|
$resetlink = new moodle_url('/mod/assign/view.php', ['id' => $this->cmid, 'action' => 'grading']);
|
||||||
|
$groupid = groups_get_course_group($course, true);
|
||||||
|
$userselector = new \core_course\output\actionbar\user_selector(
|
||||||
|
course: $course,
|
||||||
|
resetlink: $resetlink,
|
||||||
|
userid: $userid,
|
||||||
|
groupid: $groupid,
|
||||||
|
usersearch: $usersearch,
|
||||||
|
instanceid: $assignid
|
||||||
|
);
|
||||||
|
$data['userselector'] = $actionbarrenderer->render($userselector);
|
||||||
|
|
||||||
if ($course->groupmode) {
|
if ($course->groupmode) {
|
||||||
$actionbarrenderer = $PAGE->get_renderer('core_course', 'actionbar');
|
|
||||||
$data['groupselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\group_selector($course));
|
$data['groupselector'] = $actionbarrenderer->render(new \core_course\output\actionbar\group_selector($course));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,12 @@ class assign_grading_table extends table_sql implements renderable {
|
||||||
$this->rownum = $rowoffset - 1;
|
$this->rownum = $rowoffset - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userid = optional_param('userid', null, PARAM_INT);
|
||||||
|
$groupid = groups_get_course_group($assignment->get_course(), true);
|
||||||
|
// If the user ID is set, it indicates that a user has been selected. In this case, override the user search
|
||||||
|
// string with the full name of the selected user.
|
||||||
|
$usersearch = $userid ? fullname(\core_user::get_user($userid)) : optional_param('search', '', PARAM_NOTAGS);
|
||||||
|
$assignment->set_usersearch($userid, $groupid, $usersearch);
|
||||||
$users = array_keys( $assignment->list_participants($currentgroup, true));
|
$users = array_keys( $assignment->list_participants($currentgroup, true));
|
||||||
if (count($users) == 0) {
|
if (count($users) == 0) {
|
||||||
// Insert a record that will never match to the sql is still valid.
|
// Insert a record that will never match to the sql is still valid.
|
||||||
|
|
|
@ -208,6 +208,9 @@ class assign {
|
||||||
/** @var float grade value. */
|
/** @var float grade value. */
|
||||||
public $grade;
|
public $grade;
|
||||||
|
|
||||||
|
/** @var array $usersearch The content that the current user is looking for. */
|
||||||
|
protected array $usersearch = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the base assign class.
|
* Constructor for the base assign class.
|
||||||
*
|
*
|
||||||
|
@ -337,6 +340,21 @@ class assign {
|
||||||
$this->course = $course;
|
$this->course = $course;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set usersearch to limit results when getting list of participants.
|
||||||
|
*
|
||||||
|
* @param int|null $userid User id to search for.
|
||||||
|
* @param int|null $groupid Group id to limit resuts to specific group.
|
||||||
|
* @param string $usersearch Search string to limit results.
|
||||||
|
*/
|
||||||
|
public function set_usersearch(?int $userid, ?int $groupid, string $usersearch = ''): void {
|
||||||
|
$usersearcharray = [];
|
||||||
|
$usersearcharray['userid'] = $userid;
|
||||||
|
$usersearcharray['groupid'] = $groupid;
|
||||||
|
$usersearcharray['usersearch'] = $usersearch;
|
||||||
|
$this->usersearch = $usersearcharray;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set error message.
|
* Set error message.
|
||||||
*
|
*
|
||||||
|
@ -2320,6 +2338,21 @@ class assign {
|
||||||
$params['markerid'] = $USER->id;
|
$params['markerid'] = $USER->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A user wants to view a particular user rather than a set of users.
|
||||||
|
if ($this->usersearch) {
|
||||||
|
if (isset($this->usersearch['userid'])) {
|
||||||
|
$additionalfilters .= " AND u.id = :uid";
|
||||||
|
$params['uid'] = $this->usersearch['userid'];
|
||||||
|
} else if ($this->usersearch['usersearch'] !== '') { // A user wants to view a subset of learners that match the search criteria.
|
||||||
|
[
|
||||||
|
'where' => $keywordswhere,
|
||||||
|
'params' => $keywordsparams,
|
||||||
|
] = \core_user::get_users_search_sql($this->context, $this->usersearch['usersearch']);
|
||||||
|
$additionalfilters .= " AND $keywordswhere";
|
||||||
|
$params = array_merge($params, $keywordsparams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$sql = "SELECT $fields
|
$sql = "SELECT $fields
|
||||||
FROM {user} u
|
FROM {user} u
|
||||||
JOIN ($esql UNION $ssql) je ON je.id = u.id
|
JOIN ($esql UNION $ssql) je ON je.id = u.id
|
||||||
|
@ -4570,6 +4603,8 @@ class assign {
|
||||||
$currenturl = new moodle_url('/mod/assign/view.php', ['id' => $this->get_course_module()->id, 'action' => 'grading']);
|
$currenturl = new moodle_url('/mod/assign/view.php', ['id' => $this->get_course_module()->id, 'action' => 'grading']);
|
||||||
$PAGE->activityheader->set_attrs(['hidecompletion' => true]);
|
$PAGE->activityheader->set_attrs(['hidecompletion' => true]);
|
||||||
|
|
||||||
|
$PAGE->requires->js_call_amd('mod_assign/user', 'init', [$currenturl->out(false)]);
|
||||||
|
|
||||||
// Conditionally add the group JS if we have groups enabled.
|
// Conditionally add the group JS if we have groups enabled.
|
||||||
if ($this->get_course()->groupmode) {
|
if ($this->get_course()->groupmode) {
|
||||||
$PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$currenturl->out(false)]);
|
$PAGE->requires->js_call_amd('core_course/actionbar/group', 'init', [$currenturl->out(false)]);
|
||||||
|
|
|
@ -23,10 +23,12 @@
|
||||||
* none
|
* none
|
||||||
|
|
||||||
Context variables required for this template:
|
Context variables required for this template:
|
||||||
|
* userselector - HTML that outputs the user selector
|
||||||
* groupselector - (optional) HTML that outputs the group selector
|
* groupselector - (optional) HTML that outputs the group selector
|
||||||
|
|
||||||
Example context (json):
|
Example context (json):
|
||||||
{
|
{
|
||||||
|
"userselector": "<div class='user-search'></div>",
|
||||||
"groupselector": "<div class='group-selector'></div>",
|
"groupselector": "<div class='group-selector'></div>",
|
||||||
"pagereset": "http://moodle.local/mod/assign/view.php?id=2&action=grading&group=0",
|
"pagereset": "http://moodle.local/mod/assign/view.php?id=2&action=grading&group=0",
|
||||||
"downloadall": "https://moodle.org"
|
"downloadall": "https://moodle.org"
|
||||||
|
@ -39,6 +41,12 @@
|
||||||
<h2>{{#str}}gradeitem:submissions, mod_assign{{/str}}</h2>
|
<h2>{{#str}}gradeitem:submissions, mod_assign{{/str}}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="navitem-divider d-none d-sm-flex"></div>
|
<div class="navitem-divider d-none d-sm-flex"></div>
|
||||||
|
{{#userselector}}
|
||||||
|
<div class="navitem">
|
||||||
|
{{{.}}}
|
||||||
|
</div>
|
||||||
|
<div class="navitem-divider d-none d-sm-flex"></div>
|
||||||
|
{{/userselector}}
|
||||||
{{#groupselector}}
|
{{#groupselector}}
|
||||||
<div class="navitem">
|
<div class="navitem">
|
||||||
{{{.}}}
|
{{{.}}}
|
||||||
|
|
313
mod/assign/tests/behat/submissions_user_filter.feature
Normal file
313
mod/assign/tests/behat/submissions_user_filter.feature
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
@mod @mod_assign @javascript
|
||||||
|
Feature: Within the assignment submissions page, test that we can search for users
|
||||||
|
In order to filter specific users in the assignment submissions page
|
||||||
|
As a teacher
|
||||||
|
I need to be able to see and trigger the search filter
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "courses" exist:
|
||||||
|
| fullname | shortname | category | groupmode | groupmodeforce |
|
||||||
|
| Course 1 | C1 | 0 | 1 | 1 |
|
||||||
|
And the following "users" exist:
|
||||||
|
| username | firstname | lastname | email | idnumber | phone1 | phone2 | department | institution | city | country |
|
||||||
|
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | 1234567892 | 1234567893 | ABC1 | ABCD | Perth | AU |
|
||||||
|
| student1 | Student | 1 | student1@example.com | s1 | 3213078612 | 8974325612 | ABC1 | ABCD | Hanoi | VN |
|
||||||
|
| student2 | Dummy | User | student2@example.com | s2 | 4365899871 | 7654789012 | ABC2 | ABCD | Tokyo | JP |
|
||||||
|
| student3 | User | Example | student3@example.com | s3 | 3243249087 | 0875421745 | ABC2 | ABCD | Olney | GB |
|
||||||
|
| student4 | User | Test | student4@example.com | s4 | 0987532523 | 2149871323 | ABC3 | ABCD | Tokyo | JP |
|
||||||
|
| student5 | Turtle | Manatee | student5@example.com | s5 | 1239087780 | 9873623589 | ABC3 | ABCD | Perth | AU |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| teacher1 | C1 | editingteacher |
|
||||||
|
| student1 | C1 | student |
|
||||||
|
| student2 | C1 | student |
|
||||||
|
| student3 | C1 | student |
|
||||||
|
| student4 | C1 | student |
|
||||||
|
| student5 | C1 | student |
|
||||||
|
And the following "groups" exist:
|
||||||
|
| name | course | idnumber |
|
||||||
|
| Default group | C1 | dg |
|
||||||
|
| Advanced group | C1 | ag |
|
||||||
|
And the following "group members" exist:
|
||||||
|
| user | group |
|
||||||
|
| student3 | ag |
|
||||||
|
| student5 | dg |
|
||||||
|
And the following "activities" exist:
|
||||||
|
| activity | course | name |
|
||||||
|
| assign | C1 | Test assignment one |
|
||||||
|
And the following config values are set as admin:
|
||||||
|
| showuseridentity | idnumber,email,city,country,phone1,phone2,department,institution |
|
||||||
|
And I am on the "Test assignment one" Activity page logged in as teacher1
|
||||||
|
And I follow "View all submissions"
|
||||||
|
And I change window size to "large"
|
||||||
|
|
||||||
|
Scenario: A teacher can view and trigger the user search
|
||||||
|
# Check the placeholder text
|
||||||
|
Given I should see "Search users"
|
||||||
|
# Confirm the search is currently inactive and results are unfiltered.
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Turtle Manatee |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Dummy User |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
When I set the field "Search users" to "Turtle"
|
||||||
|
And I wait until "View all results (1)" "option_role" exists
|
||||||
|
And I confirm "Turtle Manatee" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Example" does not exist in the "Search users" search combo box
|
||||||
|
And I click on "Turtle Manatee" "list_item"
|
||||||
|
# Business case: This will trigger a page reload and can not dynamically update the table.
|
||||||
|
And I wait until the page is ready
|
||||||
|
Then the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Turtle Manatee |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Dummy User |
|
||||||
|
And I set the field "Search users" to "Turt"
|
||||||
|
And I wait until "View all results (1)" "option_role" exists
|
||||||
|
And I click on "Clear search input" "button" in the ".user-search" "css_element"
|
||||||
|
And "View all results (1)" "option_role" should not be visible
|
||||||
|
|
||||||
|
Scenario: A teacher can search to find specified users
|
||||||
|
# Case: Standard search.
|
||||||
|
Given I set the field "Search users" to "Dummy User"
|
||||||
|
And I wait until "View all results (1)" "option_role" exists
|
||||||
|
When I click on "Dummy User" "list_item"
|
||||||
|
Then the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Dummy User |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Turtle Manatee |
|
||||||
|
|
||||||
|
# Case: No users found.
|
||||||
|
When I set the field "Search users" to "Plagiarism"
|
||||||
|
And I should see "No results for \"Plagiarism\""
|
||||||
|
# Table remains unchanged as the user had no results to select from the dropdown.
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Dummy User |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Turtle Manatee |
|
||||||
|
|
||||||
|
# Case: Multiple users found and select only one result.
|
||||||
|
Then I set the field "Search users" to "User"
|
||||||
|
And I wait until "View all results (3)" "option_role" exists
|
||||||
|
And I confirm "Dummy User" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Example" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Turtle Manatee" does not exist in the "Search users" search combo box
|
||||||
|
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
|
||||||
|
And I confirm "User (student2@example.com)" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User (student3@example.com)" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User (student4@example.com)" exists in the "Search users" search combo box
|
||||||
|
And I click on "Dummy User" "list_item"
|
||||||
|
And I wait until the page is ready
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Dummy User |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Turtle Manatee |
|
||||||
|
# Business case: When searching with multiple partial matches, show the matches in the dropdown + a "View all results for (Bob)"
|
||||||
|
# Business case cont. When pressing enter with multiple partial matches, behave like when you select the "View all results for (Bob)"
|
||||||
|
# Case: Multiple users found and select all partial matches.
|
||||||
|
And I set the field "Search users" to "User"
|
||||||
|
And I wait until "View all results (3)" "option_role" exists
|
||||||
|
# Dont need to check if all users are in the dropdown, we checked that earlier in this test.
|
||||||
|
And I click on "View all results (3)" "option_role"
|
||||||
|
And I wait until the page is ready
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Dummy User |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| Turtle Manatee |
|
||||||
|
And I click on "Clear" "link" in the ".user-search" "css_element"
|
||||||
|
And I wait until the page is ready
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Turtle Manatee |
|
||||||
|
| Student 1 |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Dummy User |
|
||||||
|
|
||||||
|
Scenario: A teacher can quickly tell that a search is active on the current table
|
||||||
|
When I click on "Turtle" in the "Search users" search combo box
|
||||||
|
# The search input should contain the name of the user we have selected, so that it is clear that the result pertains to a specific user.
|
||||||
|
Then the field "Search users" matches value "Turtle Manatee"
|
||||||
|
# Test if we can then further retain the turtle result set and further filter from there.
|
||||||
|
And I set the field "Search users" to "Turtle plagiarism"
|
||||||
|
And "Turtle Manatee" "list_item" should not be visible
|
||||||
|
And I should see "No results for \"Turtle plagiarism\""
|
||||||
|
|
||||||
|
Scenario: A teacher can search for values besides the users' name
|
||||||
|
Given I set the field "Search users" to "student5@example.com"
|
||||||
|
And I wait until "View all results (1)" "option_role" exists
|
||||||
|
And "Turtle Manatee" "list_item" should exist
|
||||||
|
And I set the field "Search users" to "@example.com"
|
||||||
|
And I wait until "View all results (5)" "option_role" exists
|
||||||
|
# Note: All learners match this email & showing emails is current default.
|
||||||
|
And I confirm "Dummy User" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Example" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Student 1" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Turtle Manatee" exists in the "Search users" search combo box
|
||||||
|
# Search on the country field.
|
||||||
|
When I set the field "Search users" to "JP"
|
||||||
|
And I wait until "Turtle Manatee" "list_item" does not exist
|
||||||
|
And I confirm "Dummy User" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
# Search on the city field.
|
||||||
|
And I set the field "Search users" to "Hanoi"
|
||||||
|
And I wait until "User Test" "list_item" does not exist
|
||||||
|
And I confirm "Student 1" exists in the "Search users" search combo box
|
||||||
|
# Search on the institution field.
|
||||||
|
And I set the field "Search users" to "ABCD"
|
||||||
|
And I wait until "Dummy User" "list_item" exists
|
||||||
|
And I confirm "User Example" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Student 1" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Turtle Manatee" exists in the "Search users" search combo box
|
||||||
|
# Search on the department field.
|
||||||
|
And I set the field "Search users" to "ABC3"
|
||||||
|
And I wait until "User Example" "list_item" does not exist
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Turtle Manatee" exists in the "Search users" search combo box
|
||||||
|
# Search on the phone1 field.
|
||||||
|
And I set the field "Search users" to "4365899871"
|
||||||
|
And I wait until "User Test" "list_item" does not exist
|
||||||
|
And I confirm "Dummy User" exists in the "Search users" search combo box
|
||||||
|
# Search on the phone2 field.
|
||||||
|
And I set the field "Search users" to "2149871323"
|
||||||
|
And I wait until "Dummy User" "list_item" does not exist
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
# Search on the institution field then press enter to show the record set.
|
||||||
|
And I set the field "Search users" to "ABC"
|
||||||
|
And I wait until "Turtle Manatee" "list_item" exists
|
||||||
|
And I confirm "Dummy User" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Example" exists in the "Search users" search combo box
|
||||||
|
And I confirm "User Test" exists in the "Search users" search combo box
|
||||||
|
And I confirm "Student 1" exists in the "Search users" search combo box
|
||||||
|
And I press the down key
|
||||||
|
And I press the enter key
|
||||||
|
And I wait "1" seconds
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Student 1 |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
| Dummy User |
|
||||||
|
| Turtle Manatee |
|
||||||
|
| Teacher 1 |
|
||||||
|
|
||||||
|
Scenario: A teacher can set focus and search using the input are with a keyboard
|
||||||
|
Given I set the field "Search users" to "ABC"
|
||||||
|
And the focused element is "Search users" "field"
|
||||||
|
And I wait until "Turtle Manatee" "option_role" exists
|
||||||
|
# Basic tests for the page.
|
||||||
|
When I press the down key
|
||||||
|
And ".active" "css_element" should exist in the "Student 1" "option_role"
|
||||||
|
And I press the up key
|
||||||
|
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
|
||||||
|
And I press the down key
|
||||||
|
And ".active" "css_element" should exist in the "Student 1" "option_role"
|
||||||
|
And I press the escape key
|
||||||
|
And the focused element is "Search users" "field"
|
||||||
|
Then I set the field "Search users" to "Goodmeme"
|
||||||
|
And I press the down key
|
||||||
|
And the focused element is "Search users" "field"
|
||||||
|
And I set the field "Search users" to "ABC"
|
||||||
|
And I wait until "Turtle Manatee" "option_role" exists
|
||||||
|
And I press the down key
|
||||||
|
And ".active" "css_element" should exist in the "Student 1" "option_role"
|
||||||
|
# Lets check the tabbing order.
|
||||||
|
And I set the field "Search users" to "ABC"
|
||||||
|
And I click on "Search users" "field"
|
||||||
|
And I wait until "Turtle Manatee" "option_role" exists
|
||||||
|
And I press the tab key
|
||||||
|
And the focused element is "Clear search input" "button"
|
||||||
|
And I press the tab key
|
||||||
|
And ".groupsearchwidget" "css_element" should exist
|
||||||
|
# Ensure we can interact with the input & clear search options with the keyboard.
|
||||||
|
# Space & Enter have the same handling for triggering the two functionalities.
|
||||||
|
And I set the field "Search users" to "User"
|
||||||
|
And I press the up key
|
||||||
|
And I press the enter key
|
||||||
|
And I wait to be redirected
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Dummy User |
|
||||||
|
| User Example |
|
||||||
|
| User Test |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| Turtle Manatee |
|
||||||
|
|
||||||
|
Scenario: Once a teacher searches, it'll apply the currently set filters and inform the teacher as such
|
||||||
|
# Set up a basic filtering case.
|
||||||
|
Given I click on "Advanced group" in the "Search groups" search combo box
|
||||||
|
And the following should exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| User Example |
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Teacher 1 |
|
||||||
|
| Student 1 |
|
||||||
|
| User Test |
|
||||||
|
| Dummy User |
|
||||||
|
| Turtle Manatee |
|
||||||
|
# Begin the search checking if we are adhering the filters.
|
||||||
|
When I set the field "Search users" to "Turtle"
|
||||||
|
Then I confirm "Turtle Manatee" does not exist in the "Search users" search combo box
|
||||||
|
|
||||||
|
Scenario: As a teacher I can dynamically find users whilst ignoring pagination
|
||||||
|
Given "11" "users" exist with the following data:
|
||||||
|
| username | students[count] |
|
||||||
|
| firstname | Student |
|
||||||
|
| lastname | s[count] |
|
||||||
|
| email | students[count]@example.com |
|
||||||
|
And "11" "course enrolments" exist with the following data:
|
||||||
|
| user | students[count] |
|
||||||
|
| course | C1 |
|
||||||
|
| role |student |
|
||||||
|
And I reload the page
|
||||||
|
And the field "perpage" matches value "10"
|
||||||
|
And the following should not exist in the "generaltable" table:
|
||||||
|
| -1- |
|
||||||
|
| Student s11 |
|
||||||
|
When I set the field "Search users" to "11"
|
||||||
|
# One of the users' phone numbers also matches.
|
||||||
|
And I wait until "View all results (1)" "option_role" exists
|
||||||
|
Then I confirm "Student s11" exists in the "Search users" search combo box
|
|
@ -40,6 +40,7 @@
|
||||||
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}"></span>
|
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}"></span>
|
||||||
<span class="d-none" data-region="groupid" data-groupid="{{group}}"></span>
|
<span class="d-none" data-region="groupid" data-groupid="{{group}}"></span>
|
||||||
<span class="d-none" data-region="instance" data-instance="{{instance}}"></span>
|
<span class="d-none" data-region="instance" data-instance="{{instance}}"></span>
|
||||||
|
<span class="d-none" data-region="currentvalue" data-currentvalue="{{currentvalue}}"></span>
|
||||||
{{< core/search_input_auto }}
|
{{< core/search_input_auto }}
|
||||||
{{$label}}{{#str}}searchusers, core{{/str}}{{/label}}
|
{{$label}}{{#str}}searchusers, core{{/str}}{{/label}}
|
||||||
{{$placeholder}}{{#str}}searchusers, core{{/str}}{{/placeholder}}
|
{{$placeholder}}{{#str}}searchusers, core{{/str}}{{/placeholder}}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue