MDL-77991 gradereport_user: Migrate to core search dropdown

This commit is contained in:
Mathew May
2023-08-03 09:07:18 +08:00
parent 9bb91ce870
commit 69844e76da
13 changed files with 513 additions and 214 deletions
+3
View File
@@ -0,0 +1,3 @@
define("gradereport_user/group",["exports","core_group/comboboxsearch/group","core/url"],(function(_exports,_group,_url){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_group=_interopRequireDefault(_group),_url=_interopRequireDefault(_url);class Group extends _group.default{constructor(){var obj,key,value;super(),value=void 0,(key="courseID")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.selectors={...this.selectors,courseid:'[data-region="courseid"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid}static init(){return new Group}selectOneLink(groupID){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,groupsearchvalue:this.getSearchTerm(),group:groupID},!1)}}return _exports.default=Group,_exports.default}));
//# sourceMappingURL=group.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"group.min.js","sources":["../src/group.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 * Allow the user to search for groups within the user report.\n *\n * @module gradereport_user/group\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport GroupSearch from 'core_group/comboboxsearch/group';\nimport Url from 'core/url';\n\nexport default class Group extends GroupSearch {\n\n courseID;\n\n constructor() {\n super();\n\n // Define our standard lookups.\n this.selectors = {...this.selectors,\n courseid: '[data-region=\"courseid\"]',\n };\n const component = document.querySelector(this.componentSelector());\n this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;\n }\n\n static init() {\n return new Group();\n }\n\n /**\n * Build up the view all link that is dedicated to a particular result.\n *\n * @param {Number} groupID The ID of the group selected.\n * @returns {string|*}\n */\n selectOneLink(groupID) {\n return Url.relativeUrl('/grade/report/user/index.php', {\n id: this.courseID,\n groupsearchvalue: this.getSearchTerm(),\n group: groupID,\n }, false);\n }\n}\n"],"names":["Group","GroupSearch","constructor","selectors","this","courseid","component","document","querySelector","componentSelector","courseID","dataset","selectOneLink","groupID","Url","relativeUrl","id","groupsearchvalue","getSearchTerm","group"],"mappings":"yWAyBqBA,cAAcC,eAI/BC,6LAISC,UAAY,IAAIC,KAAKD,UACtBE,SAAU,kCAERC,UAAYC,SAASC,cAAcJ,KAAKK,0BACzCC,SAAWJ,UAAUE,cAAcJ,KAAKD,UAAUE,UAAUM,QAAQN,8BAIlE,IAAIL,MASfY,cAAcC,gBACHC,aAAIC,YAAY,+BAAgC,CACnDC,GAAIZ,KAAKM,SACTO,iBAAkBb,KAAKc,gBACvBC,MAAON,UACR"}
+4 -4
View File
@@ -1,10 +1,10 @@
define("gradereport_user/user",["exports","core/local/aria/focuslock","core/pending","core/templates","core_grades/searchwidget/repository","core_grades/searchwidget/basewidget","core/str","core/url","jquery","core_grades/searchwidget/selectors"],(function(_exports,FocusLockManager,_pending,Templates,Repository,WidgetBase,_str,_url,_jquery,Selectors){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default: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)}function _interopRequireWildcard(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]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
define("gradereport_user/user",["exports","core_user/comboboxsearch/user","core/url","core/templates","core_grades/searchwidget/repository"],(function(_exports,_user,_url,_templates,Repository){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* A widget to search users within the gradebook.
* Allow the user to search for learners within the user report.
*
* @module gradereport_user/user
* @copyright 2022 Mathew May <mathew.solutions>
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,FocusLockManager=_interopRequireWildcard(FocusLockManager),_pending=_interopRequireDefault(_pending),Templates=_interopRequireWildcard(Templates),Repository=_interopRequireWildcard(Repository),WidgetBase=_interopRequireWildcard(WidgetBase),_url=_interopRequireDefault(_url),_jquery=_interopRequireDefault(_jquery),Selectors=_interopRequireWildcard(Selectors);_exports.init=()=>{const pendingPromise=new _pending.default;registerListenerEvents(),pendingPromise.resolve()};const registerListenerEvents=()=>{let{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}=WidgetBase.promisesAndResolvers();const dropdownMenuContainer=document.querySelector(Selectors.elements.getSearchWidgetDropdownSelector("user")),menuContainer=document.querySelector(Selectors.elements.getSearchWidgetSelector("user")),inputElement=menuContainer.querySelector('input[name="userid"]');(0,_jquery.default)(menuContainer).on("show.bs.dropdown",(async e=>{const courseID=e.relatedTarget.dataset.courseid,groupId=e.relatedTarget.dataset.groupid;await WidgetBase.showLoader(dropdownMenuContainer);const data=await Repository.userFetch(courseID,groupId).catch((async e=>{const errorTemplateData={errormessage:e.message};bodyPromiseResolver(await Templates.render("core_grades/searchwidget/error",errorTemplateData))}));if(data===[])return;const allUsersOptionName=await(0,_str.get_string)("allusersnum","gradereport_user",data.users.length),allUsersOption=await Templates.render("gradereport_user/all_users_item",{id:0,name:allUsersOptionName,url:_url.default.relativeUrl("/grade/report/user/index.php",{id:courseID,userid:0},!1)});await WidgetBase.init(dropdownMenuContainer,bodyPromise,data.users,searchUsers(),allUsersOption,afterSelect),bodyPromiseResolver(Templates.render("core_grades/searchwidget/user/usersearch_body",{displayunsearchablecontent:!0})),FocusLockManager.trapFocus(dropdownMenuContainer)})),(0,_jquery.default)(menuContainer).on("hide.bs.dropdown",(()=>{FocusLockManager.untrapFocus()})),inputElement.addEventListener("change",(e=>{const courseID=menuContainer.querySelector(".dropdown-toggle").dataset.courseid,actionUrl=_url.default.relativeUrl("/grade/report/user/index.php",{id:courseID,userid:e.target.value},!1);location.href=actionUrl,e.stopPropagation()}))},searchUsers=()=>()=>(users,searchTerm)=>{if(""===searchTerm)return users;searchTerm=searchTerm.toLowerCase();const searchResults=[];return users.forEach((user=>{user.fullname.toLowerCase().includes(searchTerm)&&searchResults.push(user)})),searchResults},afterSelect=selected=>{const menuContainer=document.querySelector(Selectors.elements.getSearchWidgetSelector("user")),inputElement=menuContainer.querySelector('input[name="userid"]');(0,_jquery.default)(menuContainer).dropdown("hide"),inputElement.value!=selected&&(inputElement.value=selected,inputElement.dispatchEvent(new Event("change",{bubbles:!0})))}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_user=_interopRequireDefault(_user),_url=_interopRequireDefault(_url),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);class User extends _user.default{constructor(){super()}static init(){return new User}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_user/comboboxsearch/resultset",{users:this.getMatchedResults().slice(0,5),hasresults:this.getMatchedResults().length>0,matches:this.getDatasetSize(),searchterm:this.getSearchTerm(),selectall:this.selectAllResultsLink()});(0,_templates.replaceNodeContents)(this.getHTMLElements().searchDropdown,html,js)}selectAllResultsLink(){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,userid:0,searchvalue:this.getSearchTerm()},!1)}selectOneLink(userID){return _url.default.relativeUrl("/grade/report/user/index.php",{id:this.courseID,searchvalue:this.getSearchTerm(),userid:userID},!1)}fetchDataset(){const gts="string"==typeof this.groupID&&""===this.groupID?0:this.groupID;return Repository.userFetch(this.courseID,gts).then((r=>r.users))}}return _exports.default=User,_exports.default}));
//# sourceMappingURL=user.min.js.map
File diff suppressed because one or more lines are too long
+58
View File
@@ -0,0 +1,58 @@
// 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/>.
/**
* Allow the user to search for groups within the user report.
*
* @module gradereport_user/group
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import GroupSearch from 'core_group/comboboxsearch/group';
import Url from 'core/url';
export default class Group extends GroupSearch {
courseID;
constructor() {
super();
// Define our standard lookups.
this.selectors = {...this.selectors,
courseid: '[data-region="courseid"]',
};
const component = document.querySelector(this.componentSelector());
this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;
}
static init() {
return new Group();
}
/**
* Build up the view all link that is dedicated to a particular result.
*
* @param {Number} groupID The ID of the group selected.
* @returns {string|*}
*/
selectOneLink(groupID) {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
groupsearchvalue: this.getSearchTerm(),
group: groupID,
}, false);
}
}
+65 -136
View File
@@ -14,147 +14,76 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A widget to search users within the gradebook.
* Allow the user to search for learners within the user report.
*
* @module gradereport_user/user
* @copyright 2022 Mathew May <mathew.solutions>
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as FocusLockManager from 'core/local/aria/focuslock';
import Pending from 'core/pending';
import * as Templates from 'core/templates';
import * as Repository from 'core_grades/searchwidget/repository';
import * as WidgetBase from 'core_grades/searchwidget/basewidget';
import {get_string as getString} from 'core/str';
import UserSearch from 'core_user/comboboxsearch/user';
import Url from 'core/url';
import $ from 'jquery';
import * as Selectors from 'core_grades/searchwidget/selectors';
import {renderForPromise, replaceNodeContents} from 'core/templates';
import * as Repository from 'core_grades/searchwidget/repository';
/**
* Our entry point into starting to build the search widget.
* It'll eventually, based upon the listeners, open the search widget and allow filtering.
*
* @method init
*/
export const init = () => {
const pendingPromise = new Pending();
registerListenerEvents();
pendingPromise.resolve();
};
export default class User extends UserSearch {
/**
* Register user search widget related event listeners.
*
* @method registerListenerEvents
*/
const registerListenerEvents = () => {
let {bodyPromiseResolver, bodyPromise} = WidgetBase.promisesAndResolvers();
const dropdownMenuContainer = document.querySelector(Selectors.elements.getSearchWidgetDropdownSelector('user'));
const menuContainer = document.querySelector(Selectors.elements.getSearchWidgetSelector('user'));
const inputElement = menuContainer.querySelector('input[name="userid"]');
// Handle the 'shown.bs.dropdown' event (Fired when the dropdown menu is fully displayed).
$(menuContainer).on('show.bs.dropdown', async(e) => {
const courseID = e.relatedTarget.dataset.courseid;
const groupId = e.relatedTarget.dataset.groupid;
// Display a loading icon in the dropdown menu container until the body promise is resolved.
await WidgetBase.showLoader(dropdownMenuContainer);
// If an error occurs while fetching the data, display the error within the dropdown menu.
const data = await Repository.userFetch(courseID, groupId).catch(async(e) => {
const errorTemplateData = {
'errormessage': e.message
};
bodyPromiseResolver(
await Templates.render('core_grades/searchwidget/error', errorTemplateData)
);
});
// Early return if there is no module data.
if (data === []) {
return;
}
// The HTML for the 'All users' option which will be rendered in the non-searchable content are of the widget.
const allUsersOptionName = await getString('allusersnum', 'gradereport_user', data.users.length);
const allUsersOption = await Templates.render('gradereport_user/all_users_item', {
id: 0,
name: allUsersOptionName,
url: Url.relativeUrl('/grade/report/user/index.php', {id: courseID, userid: 0}, false),
});
await WidgetBase.init(
dropdownMenuContainer,
bodyPromise,
data.users,
searchUsers(),
allUsersOption,
afterSelect
);
// Resolvers for passed functions in the dropdown menu creation.
bodyPromiseResolver(Templates.render(
'core_grades/searchwidget/user/usersearch_body', {displayunsearchablecontent: true}
));
// Lock tab control. It has to be locked because the dropdown's role is dialog.
FocusLockManager.trapFocus(dropdownMenuContainer);
});
// Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).
$(menuContainer).on('hide.bs.dropdown', () => {
FocusLockManager.untrapFocus();
});
inputElement.addEventListener('change', e => {
const toggle = menuContainer.querySelector('.dropdown-toggle');
const courseID = toggle.dataset.courseid;
const actionUrl = Url.relativeUrl('/grade/report/user/index.php', {id: courseID, userid: e.target.value}, false);
location.href = actionUrl;
e.stopPropagation();
});
};
/**
* Define how we want to search and filter users when the user decides to input a search value.
*
* @method searchUsers
* @returns {function(): function(*, *): (*)}
*/
const searchUsers = () => {
return () => {
return (users, searchTerm) => {
if (searchTerm === '') {
return users;
}
searchTerm = searchTerm.toLowerCase();
const searchResults = [];
users.forEach((user) => {
const userName = user.fullname.toLowerCase();
if (userName.includes(searchTerm)) {
searchResults.push(user);
}
});
return searchResults;
};
};
};
/**
* Define the action to be performed when an item is selected by the search widget.
*
* @param {String} selected The selected item's value.
*/
const afterSelect = (selected) => {
const menuContainer = document.querySelector(Selectors.elements.getSearchWidgetSelector('user'));
const inputElement = menuContainer.querySelector('input[name="userid"]');
$(menuContainer).dropdown('hide'); // Otherwise the dropdown stays open when user choose an option using keyboard.
if (inputElement.value != selected) {
inputElement.value = selected;
inputElement.dispatchEvent(new Event('change', {bubbles: true}));
constructor() {
super();
}
};
static init() {
return new User();
}
/**
* Build the content then replace the node.
*/
async renderDropdown() {
const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', {
users: this.getMatchedResults().slice(0, 5),
hasresults: this.getMatchedResults().length > 0,
matches: this.getDatasetSize(),
searchterm: this.getSearchTerm(),
selectall: this.selectAllResultsLink(),
});
replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);
}
/**
* Build up the view all link.
*
* @returns {string|*}
*/
selectAllResultsLink() {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
userid: 0,
searchvalue: this.getSearchTerm()
}, false);
}
/**
* Build up the view all link that is dedicated to a particular result.
*
* @param {Number} userID The ID of the user selected.
* @returns {string|*}
*/
selectOneLink(userID) {
return Url.relativeUrl('/grade/report/user/index.php', {
id: this.courseID,
searchvalue: this.getSearchTerm(),
userid: userID,
}, false);
}
/**
* Get the data we will be searching against in this component.
*
* @returns {Promise<*>}
*/
fetchDataset() {
// Small typing checks as sometimes groups don't exist therefore the element returns a empty string.
const gts = typeof (this.groupID) === "string" && this.groupID === '' ? 0 : this.groupID;
return Repository.userFetch(this.courseID, gts).then((r) => r.users);
}
}
@@ -80,12 +80,12 @@ class action_bar extends \core_grades\output\action_bar {
// If the user has the capability to view all grades, display the group selector (if applicable), the user selector
// and the view mode selector (if applicable).
if (has_capability('moodle/grade:viewall', $this->context)) {
$course = get_course($courseid);
$gradesrenderer = $PAGE->get_renderer('core_grades');
$userreportrenderer = $PAGE->get_renderer('gradereport_user');
$data['groupselector'] = $gradesrenderer->group_selector($course);
$data['userselector'] = $userreportrenderer->users_selector($course, $this->userid, $this->currentgroupid);
$data['groupselector'] = $PAGE->get_renderer('core_grades')->group_selector(get_course($courseid));
$data['userselector'] = [
'courseid' => $courseid,
'content' => $userreportrenderer->users_selector(get_course($courseid), $this->userid, $this->currentgroupid)
];
// Do not output the 'view mode' selector when in zero state or when the current user is viewing its own report.
if (!is_null($this->userid) && $USER->id != $this->userid) {
+4
View File
@@ -93,6 +93,10 @@ if (has_capability('moodle/grade:viewall', $context)) {
// Verify if we are using groups or not.
$groupmode = groups_get_course_groupmode($course);
$currentgroup = $gpr->groupid;
// Conditionally add the group JS if we have groups enabled.
if ($groupmode) {
$PAGE->requires->js_call_amd('gradereport_user/group', 'init');
}
// To make some other functions work better later.
if (!$currentgroup) {
+16 -34
View File
@@ -22,6 +22,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core\output\comboboxsearch;
/**
* Custom renderer for the user grade report
*
@@ -90,47 +92,27 @@ class gradereport_user_renderer extends plugin_renderer_base {
* @throws coding_exception
*/
public function users_selector(object $course, ?int $userid = null, ?int $groupid = null): string {
$resetlink = new moodle_url('/grade/report/user/index.php', ['id' => $course->id, 'group' => 0]);
$data = [
'currentvalue' => optional_param('searchvalue', '', PARAM_NOTAGS),
'resetlink' => $resetlink->out(false),
'name' => 'userid',
'courseid' => $course->id,
'groupid' => $groupid ?? 0,
];
// If a particular option is selected (not in zero state).
if (!is_null($userid)) {
if ($userid) { // A single user selected.
$user = core_user::get_user($userid);
$data['selectedoption'] = [
'image' => $this->user_picture($user, ['size' => 40, 'link' => false]),
'text' => fullname($user),
'additionaltext' => $user->email,
];
} else { // All users selected.
// Get the total number of users.
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
$showonlyactiveenrol = $showonlyactiveenrol ||
!has_capability('moodle/course:viewsuspendedusers', context_course::instance($course->id));
$gui = new graded_users_iterator($course, null, $groupid);
$gui->require_active_enrolment($showonlyactiveenrol);
$gui->init();
$totalusersnum = 0;
while ($userdata = $gui->next_user()) {
$totalusersnum++;
}
$gui->close();
$data['selectedoption'] = [
'text' => get_string('allusersnum', 'gradereport_user', $totalusersnum),
];
}
$data['userid'] = $userid;
}
$searchdropdown = new comboboxsearch(
true,
$this->render_from_template('core_user/comboboxsearch/user_selector', $data),
null,
'user-search dropdown d-flex',
null,
'usersearchdropdown overflow-auto',
null,
false,
);
$this->page->requires->js_call_amd('gradereport_user/user', 'init');
return $this->render_from_template('core_grades/user_selector', $data);
return $this->render_from_template($searchdropdown->get_template(), $searchdropdown->export_for_template($this));
}
/**
@@ -19,7 +19,7 @@
Context variables required for this template:
* generalnavselector - The data object containing the required properties to render the general navigation selector.
* groupselector - (optional) HTML that outputs the group selector
* userselector - (optional) HTML that outputs the user selector
* userselector - (optional) The data object containing the required properties to render the user selector
* viewasselector - (optional) HTML that outputs the 'view report as' selector
Example context (json):
@@ -63,7 +63,10 @@
]
},
"groupselector": "<div class='group-selector'></div>",
"userselector": "<div class='user-selector'></div>",
"userselector": {
"content": "<div class='user-selector'></div>",
"courseid": 25
},
"viewasselector": "<div class='view-user-selector'></div>"
}
}}
@@ -82,8 +85,11 @@
<div class="navitem-divider"></div>
{{/groupselector}}
{{#userselector}}
<div class="navitem">
{{{userselector}}}
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}" aria-hidden="true"></span>
<div class="navitem flex-column">
{{#content}}
{{{.}}}
{{/content}}
</div>
<div class="navitem-divider"></div>
{{/userselector}}
@@ -27,41 +27,91 @@ Feature: Group searching functionality within the user report.
And the "multilang" filter applies to "content and headings"
And I am on the "C1" "Course" page logged in as "teacher1"
And I change window size to "large"
And I navigate to "View > User report" in the course gradebook
Scenario: A teacher can see the 'group' search widget only when group mode is enabled in the course
Given I navigate to "View > User report" in the course gradebook
And ".search-widget[data-searchtype='group']" "css_element" should exist
Given ".groupsearchwidget" "css_element" should exist
And I am on "Course 1" course homepage
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| id_groupmode | No groups |
And I press "Save and display"
When I navigate to "View > User report" in the course gradebook
Then ".search-widget[data-searchtype='group']" "css_element" should not exist
Then ".groupsearchwidget" "css_element" should not exist
Scenario: A teacher can search for and find a group to find a user in
Given I navigate to "View > User report" in the course gradebook
And I click on ".search-widget[data-searchtype='group']" "css_element"
And I confirm "Tutor group" in "group" search within the gradebook widget exists
Given I confirm "Tutor group" in "group" search within the gradebook widget exists
And I confirm "Marker group" in "group" search within the gradebook widget exists
When I set the field "Search groups" to "tutor"
And I wait "1" seconds
Then I confirm "Tutor group" in "group" search within the gradebook widget exists
And I confirm "Marker group" in "group" search within the gradebook widget does not exist
And I click on "Tutor group" in the "group" search widget
# The search input remains in the field on reload this is in keeping with other search implementations.
And I click on ".groupsearchwidget" "css_element"
And the field "Search groups" matches value "tutor"
Then I set the field "Search groups" to "Turtle"
And I should see "No results for \"Turtle\""
Scenario: A teacher can only see the group members in the 'user' search widget after selecting a group option
Given I navigate to "View > User report" in the course gradebook
# Confirm that all users are initially displayed in the 'user' search widget.
Given I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget exists
# Select a particular group from the 'group' search widget.
When I click on "Default group" in the "group" search widget
# Confirm that only users which are members of the selected group are displayed in the 'user' search widget.
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I set the field "Search users" to "Student"
Then I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget does not exist
And I click on "Tutor group" in the "group" search widget
And I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget does not exist
And I confirm "Student 2" in "user" search within the gradebook widget does not exist
And I click on "All participants" in the "group" search widget
And I set the field "Search users" to "Student"
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget exists
@accessibility
Scenario: A teacher can set focus and search using the input with a keyboard
# Basic tests for the page.
Given I click on ".groupsearchwidget" "css_element"
And the page should meet accessibility standards
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
And I click on "Search groups" "field"
And I wait until "Default group" "option_role" exists
And I press the down key
And the focused element is "All participants" "option_role"
And I press the end key
And the focused element is "Tutor group" "option_role"
And I press the home key
And the focused element is "All participants" "option_role"
And I press the up key
And the focused element is "Tutor group" "option_role"
And I press the down key
And the focused element is "All participants" "option_role"
And I press the escape key
And the focused element is "Search groups" "field"
Then I set the field "Search groups" to "Goodmeme"
And I wait until "Tutor group" "option_role" does not exist
And I press the down key
And the focused element is "Search groups" "field"
And I navigate to "View > User report" in the course gradebook
And I click on ".groupsearchwidget" "css_element"
And I set the field "Search groups" to "Tutor"
And I wait until "All participants" "option_role" does not exist
And I press the down key
And the focused element is "Tutor group" "option_role"
# Lets check the tabbing order.
And I set the field "Search groups" to "Marker"
And I wait until "Marker group" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the enter key
And I wait until the page is ready
And ".groupsearchwidget" "css_element" should exist
+286 -19
View File
@@ -4,29 +4,296 @@ Feature: Within the User report, a teacher can search for users.
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
| Course 2 | C2 | 0 | 0 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 |
| student1 | Student | 1 | student1@example.com | s1 |
| student2 | Student | 2 | student2@example.com | s2 |
| student32 | Student | 32 | student32@example.com | s32 |
| 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 |
# Note: Add groups etc so we can test that the search ignores those filters as well if we go down the filter dataset path.
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student32 | C1 | student |
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name |
| assign | C1 | a1 | 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 "Course 1" "Course" page logged in as "teacher1"
And I change window size to "large"
And I navigate to "View > User report" in the course gradebook
Scenario: A teacher can search for and find a user to view
Given I navigate to "View > User report" in the course gradebook
When I click on ".search-widget[data-searchtype='user']" "css_element"
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 "user-grades" table:
| -1- |
| Turtle Manatee |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
When I set the field "Search users" to "Turtle"
And I wait until "View all results (5)" "option_role" exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget does not exist
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 "user-grades" table:
| -1- |
| Turtle Manatee |
And the following should not exist in the "user-grades" 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 (5)" "option_role" exists
And I click on "Clear search input" "button" in the ".user-search" "css_element"
And "View all results (5)" "option_role" should not be visible
Scenario: A teacher can search the user report to find specified users
# Case: Standard search.
Given I click on "Dummy" in the "user" search widget
And the following should exist in the "user-grades" table:
| -1- |
| Turtle Manatee |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
# 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 "user-grades" table:
| -1- |
| Turtle Manatee |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
# 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 (5)" "option_role" exists
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget does not exist
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
And I confirm "User (student2@example.com)" in "user" search within the gradebook widget exists
And I confirm "User (student3@example.com)" in "user" search within the gradebook widget exists
And I confirm "User (student4@example.com)" in "user" search within the gradebook widget exists
And I click on "Dummy User" "list_item"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Dummy User |
And the following should not exist in the "user-grades" 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 (5)" "option_role" exists
And I click on "View all results (5)" "option_role"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Dummy User |
| User Example |
| User Test |
And the following should not exist in the "user-grades" 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 "user-grades" table:
| -1- |
| Turtle Manatee |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
# Case: No users enrolled.
And I am on the "Course 2" "Course" page
And I navigate to "View > User report" in the course gradebook
And I set the field "Search users" to "a"
And I should see "No results for \"a\""
Scenario: A teacher can quickly tell that a search is active on the current table
Given I click on "Turtle" in the "user" search widget
# The search input remains in the field on reload this is in keeping with other search implementations.
When the field "Search users" matches value "Turtle"
And I wait until "View all results (5)" "link" does not exist
# Test if we can then further retain the turtle result set and further filter from there.
Then I set the field "Search users" to "Turtle plagiarism"
And "Turtle Manatee" "list_item" should not exist
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 (5)" "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" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# Search on the country field.
When I set the field "Search users" to "JP"
And I wait until "View all results (5)" "option_role" exists
And I wait until "Turtle Manatee" "list_item" does not exist
And I confirm "Dummy User" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
# 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
Then I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Student 2" in "user" search within the gradebook widget exists
And I confirm "Student 32" in "user" search within the gradebook widget exists
And I set the field "Search users" to "2"
# 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" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# 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" in "user" search within the gradebook widget exists
And I confirm "Turtle Manatee" in "user" search within the gradebook widget exists
# 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" in "user" search within the gradebook widget exists
# 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" in "user" search within the gradebook widget exists
# 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" in "user" search within the gradebook widget exists
And I confirm "User Example" in "user" search within the gradebook widget exists
And I confirm "User Test" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget exists
And I press the enter key
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Student 1 |
| User Example |
| User Test |
| Dummy User |
| Turtle Manatee |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
@accessibility
Scenario: A teacher can set focus and search using the input are with a keyboard
Given I set the field "Search users" to "ABC"
# Basic tests for the page.
And the page should meet accessibility standards
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
When I wait until "Turtle Manatee" "option_role" exists
And I press the down key
And the focused element is "Student 1" "option_role"
And I press the end key
And the focused element is "View all results (5)" "option_role"
And I press the home key
And the focused element is "Student 1" "option_role"
And I press the up key
And the focused element is "View all results (5)" "option_role"
And I press the down key
And the focused element is "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 navigate to "View > User report" in the course gradebook
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 the focused element is "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And I wait until "View all results (5)" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button" in the ".user-search" "css_element"
And I press the tab key
And the focused element is "View all results (5)" "option_role"
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 enter key
And I wait to be redirected
And the following should exist in the "user-grades" table:
| -1- |
| Dummy User |
| User Example |
| User Test |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
| Student 1 |
| Turtle Manatee |
# Sometimes with behat we get unattached nodes causing spurious failures.
And I wait "1" seconds
And I confirm "Student 2" in "user" search within the gradebook widget exists
And I confirm "Student 32" in "user" search within the gradebook widget exists
And I confirm "Student 1" in "user" search within the gradebook widget does not exist
And I set the field "Search users" to "ABC"
And I wait until "Turtle Manatee" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button" in the ".user-search" "css_element"
And I press the enter key
And I wait until the page is ready
And I confirm "Turtle Manatee" in "user" search within the gradebook widget does not exist
@@ -11,13 +11,12 @@ Feature: We can use the user report
Scenario: Verify we can view a user grade report with no users enrolled.
Given I log in as "admin"
And I am on "Course 1" course homepage
And I navigate to "View > User report" in the course gradebook
And I click on "All users (0)" in the "user" search widget
When I navigate to "View > User report" in the course gradebook
Then I should see "There are no students enrolled in this course."
Scenario: Teacher sees his last viewed user report when navigating back to the gradebook user report.
Given the following "users" exist:
| username | firstname | lastname | email |
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| teacher2 | Teacher | 2 | teacher2@example.com |
| student1 | Student | 1 | student1@example.com |