first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
The default gradebook interface that teachers will use.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
define("gradereport_grader/collapse/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A repo for the collapsing in the grader report.
*
* @module gradereport_grader/collapse/repository
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.gradeItems=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.gradeItems=courseid=>{const request={methodname:"core_grades_get_gradeitems",args:{courseid:courseid}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../../src/collapse/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 collapsing in the grader report.\n *\n * @module gradereport_grader/collapse/repository\n * @copyright 2022 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ajax from 'core/ajax';\n\n/**\n * Fetch all the information on gradeitems we'll need in the column collapser.\n *\n * @method gradeItems\n * @param {Number} courseid What course to fetch the gradeitems for\n * @return {object} jQuery promise\n */\nexport const gradeItems = (courseid) => {\n const request = {\n methodname: 'core_grades_get_gradeitems',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["courseid","request","methodname","args","ajax","call"],"mappings":";;;;;;;gKAgC2BA,iBACjBC,QAAU,CACZC,WAAY,6BACZC,KAAM,CACFH,SAAUA,kBAGXI,cAAKC,KAAK,CAACJ,UAAU"}
+10
View File
@@ -0,0 +1,10 @@
define("gradereport_grader/feedback_modal",["exports","core/modal","core/notification","core/ajax","core/templates"],(function(_exports,_modal,_notification,_ajax,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Javascript module for displaying feedback in a modal window
*
* @module gradereport_grader/feedback_modal
* @copyright 2023 Kevin Percy <kevin.percy@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modal=_interopRequireDefault(_modal),_notification=_interopRequireDefault(_notification),_ajax=_interopRequireDefault(_ajax),_templates=_interopRequireDefault(_templates);const Selectors_showFeedback='[data-action="feedback"]',fetchFeedback=(courseid,userid,itemid)=>{const request={methodname:"core_grades_get_feedback",args:{courseid:courseid,userid:userid,itemid:itemid}};return _ajax.default.call([request])[0]},registerEventListeners=()=>{document.addEventListener("click",(e=>{const showFeedbackTrigger=e.target.closest(Selectors_showFeedback);if(showFeedbackTrigger){e.preventDefault();(async(courseid,userid,itemid)=>{let feedbackData;try{feedbackData=await fetchFeedback(courseid,userid,itemid)}catch(e){return Promise.reject(e)}return _modal.default.create({removeOnClose:!0,large:!0,verticallyCentered:!0}).then((modal=>{const body=_templates.default.render("core_grades/feedback_modal",{feedbacktext:feedbackData.feedbacktext,user:{picture:feedbackData.picture,fullname:feedbackData.fullname,additionalfield:feedbackData.additionalfield}});return modal.setBody(body),modal.setTitle(feedbackData.title),modal.show(),modal}))})(showFeedbackTrigger.dataset.courseid,e.target.closest("tr").dataset.uid,e.target.closest("td").dataset.itemid).catch(_notification.default.exception)}}))};_exports.init=()=>{registerEventListeners()}}));
//# sourceMappingURL=feedback_modal.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"feedback_modal.min.js","sources":["../src/feedback_modal.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 * Javascript module for displaying feedback in a modal window\n *\n * @module gradereport_grader/feedback_modal\n * @copyright 2023 Kevin Percy <kevin.percy@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Modal from 'core/modal';\nimport Notification from 'core/notification';\nimport ajax from 'core/ajax';\nimport Templates from 'core/templates';\n\nconst Selectors = {\n showFeedback: '[data-action=\"feedback\"]'\n};\n\n/**\n * Create the modal to display the feedback.\n *\n * @param {int} courseid\n * @param {int} userid\n * @param {int} itemid\n * @returns {Promise}\n */\nconst getModal = async(courseid, userid, itemid) => {\n let feedbackData;\n\n try {\n feedbackData = await fetchFeedback(courseid, userid, itemid);\n } catch (e) {\n return Promise.reject(e);\n }\n\n return Modal.create({\n removeOnClose: true,\n large: true,\n verticallyCentered: true,\n })\n .then(modal => {\n const body = Templates.render('core_grades/feedback_modal', {\n feedbacktext: feedbackData.feedbacktext,\n user: {\n picture: feedbackData.picture,\n fullname: feedbackData.fullname,\n additionalfield: feedbackData.additionalfield,\n },\n });\n\n modal.setBody(body);\n modal.setTitle(feedbackData.title);\n modal.show();\n\n return modal;\n });\n};\n\n/**\n * Fetch the feedback data.\n *\n * @param {int} courseid\n * @param {int} userid\n * @param {int} itemid\n * @returns {Promise}\n */\nconst fetchFeedback = (courseid, userid, itemid) => {\n const request = {\n methodname: 'core_grades_get_feedback',\n args: {\n courseid: courseid,\n userid: userid,\n itemid: itemid,\n },\n };\n return ajax.call([request])[0];\n};\n\n/**\n * Register event listeners for the View Feedback links.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const showFeedbackTrigger = e.target.closest(Selectors.showFeedback);\n if (showFeedbackTrigger) {\n e.preventDefault();\n\n const courseid = showFeedbackTrigger.dataset.courseid;\n const userid = e.target.closest('tr').dataset.uid;\n const itemid = e.target.closest('td').dataset.itemid;\n\n getModal(courseid, userid, itemid)\n .catch(Notification.exception);\n }\n });\n};\n\n/**\n * Initialize module\n */\nexport const init = () => {\n registerEventListeners();\n};\n"],"names":["Selectors","fetchFeedback","courseid","userid","itemid","request","methodname","args","ajax","call","registerEventListeners","document","addEventListener","e","showFeedbackTrigger","target","closest","preventDefault","async","feedbackData","Promise","reject","Modal","create","removeOnClose","large","verticallyCentered","then","modal","body","Templates","render","feedbacktext","user","picture","fullname","additionalfield","setBody","setTitle","title","show","getModal","dataset","uid","catch","Notification","exception"],"mappings":";;;;;;;oQA2BMA,uBACY,2BAmDZC,cAAgB,CAACC,SAAUC,OAAQC,gBAC/BC,QAAU,CACZC,WAAY,2BACZC,KAAM,CACFL,SAAUA,SACVC,OAAQA,OACRC,OAAQA,gBAGTI,cAAKC,KAAK,CAACJ,UAAU,IAM1BK,uBAAyB,KAC3BC,SAASC,iBAAiB,SAASC,UACzBC,oBAAsBD,EAAEE,OAAOC,QAAQhB,2BACzCc,oBAAqB,CACrBD,EAAEI,iBA3DGC,OAAMhB,SAAUC,OAAQC,cACjCe,iBAGAA,mBAAqBlB,cAAcC,SAAUC,OAAQC,QACvD,MAAOS,UACEO,QAAQC,OAAOR,UAGnBS,eAAMC,OAAO,CAChBC,eAAe,EACfC,OAAO,EACPC,oBAAoB,IAEvBC,MAAKC,cACIC,KAAOC,mBAAUC,OAAO,6BAA8B,CACxDC,aAAcb,aAAaa,aAC3BC,KAAM,CACFC,QAASf,aAAae,QACtBC,SAAUhB,aAAagB,SACvBC,gBAAiBjB,aAAaiB,0BAItCR,MAAMS,QAAQR,MACdD,MAAMU,SAASnB,aAAaoB,OAC5BX,MAAMY,OAECZ,UAqCHa,CAJiB3B,oBAAoB4B,QAAQxC,SAC9BW,EAAEE,OAAOC,QAAQ,MAAM0B,QAAQC,IAC/B9B,EAAEE,OAAOC,QAAQ,MAAM0B,QAAQtC,QAGzCwC,MAAMC,sBAAaC,8BAQhB,KAChBpC"}
+3
View File
@@ -0,0 +1,3 @@
define("gradereport_grader/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/grader/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 grader report.\n *\n * @module gradereport_grader/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 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/grader/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":"2WAyBqBA,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,iCAAkC,CACrDC,GAAIZ,KAAKM,SACTO,iBAAkBb,KAAKc,gBACvBC,MAAON,UACR"}
@@ -0,0 +1,10 @@
define("gradereport_grader/local/user/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj;
/**
* A repo for the search partial in the grader report.
*
* @module gradereport_grader/local/user/repository
* @copyright 2022 Mathew May <mathew.solutions>
* @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=courseid=>{const request={methodname:"gradereport_grader_get_users_in_report",args:{courseid:courseid}};return _ajax.default.call([request])[0]}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../../../src/local/user/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 grader report.\n *\n * @module gradereport_grader/local/user/repository\n * @copyright 2022 Mathew May <mathew.solutions>\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 report.\n *\n * @method userFetch\n * @param {int} courseid ID of the course to fetch the users of.\n * @return {object} jQuery promise\n */\nexport const userFetch = (courseid) => {\n const request = {\n methodname: 'gradereport_grader_get_users_in_report',\n args: {\n courseid: courseid,\n },\n };\n return ajax.call([request])[0];\n};\n"],"names":["courseid","request","methodname","args","ajax","call"],"mappings":";;;;;;;8JAgC0BA,iBAChBC,QAAU,CACZC,WAAY,yCACZC,KAAM,CACFH,SAAUA,kBAGXI,cAAKC,KAAK,CAACJ,UAAU"}
+10
View File
@@ -0,0 +1,10 @@
define("gradereport_grader/stickycolspan",["exports","jquery","core/sticky-footer"],(function(_exports,_jquery,_stickyFooter){var obj;
/**
* Javascript module for fixing the position of sticky headers with multiple colspans
*
* @module gradereport_grader/stickycolspan
* @copyright 2022 Bas Brands <bas@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const SELECTORS_GRADEPARENT=".gradeparent",SELECTORS_STUDENTHEADER="#studentheader",SELECTORS_TABLEHEADER="th.header",SELECTORS_BEHAT="body.behat-site",SELECTORS_USERDROPDOWN=".userrow th .dropdown",SELECTORS_LASTROW=".lastrow";_exports.init=()=>{if((0,_jquery.default)(SELECTORS_USERDROPDOWN).on("show.bs.dropdown hide.bs.dropdown",(e=>{e.target.closest(SELECTORS_TABLEHEADER).classList.toggle("actions-menu-active")})),defineLastRowIntersectionObserver(!0),document.addEventListener(_stickyFooter.eventTypes.stickyFooterStateChanged,(e=>{defineLastRowIntersectionObserver(e.detail.enabled)})),!document.querySelector(SELECTORS_BEHAT)){const grader=document.querySelector(SELECTORS_GRADEPARENT),tableHeaders=grader.querySelectorAll(SELECTORS_TABLEHEADER),studentHeader=grader.querySelector(SELECTORS_STUDENTHEADER),leftOffset=getComputedStyle(studentHeader).getPropertyValue("left"),rightOffset=getComputedStyle(studentHeader).getPropertyValue("right");tableHeaders.forEach((tableHeader=>{if(tableHeader.colSpan>1){const addOffset=tableHeader.offsetWidth-studentHeader.offsetWidth;window.right_to_left()?tableHeader.style.right="calc("+rightOffset+" - "+addOffset+"px )":tableHeader.style.left="calc("+leftOffset+" - "+addOffset+"px )"}}))}};const defineLastRowIntersectionObserver=stickyFooterEnabled=>{const lastRow=document.querySelector(SELECTORS_LASTROW);if(!lastRow.classList.contains("userrow")){const stickyFooterHeight=stickyFooterEnabled?document.querySelector(_stickyFooter.SELECTORS.STICKYFOOTER).offsetHeight:null;new IntersectionObserver((_ref=>{let[e]=_ref;return lastRow.classList.toggle("pinned",e.intersectionRatio<1)}),{rootMargin:stickyFooterHeight?"0px 0px -".concat(stickyFooterHeight,"px 0px"):"0px",threshold:[1]}).observe(lastRow.querySelector("th"))}}}));
//# sourceMappingURL=stickycolspan.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("gradereport_grader/user",["exports","core_user/comboboxsearch/user","core/url","gradereport_grader/local/user/repository"],(function(_exports,_user,_url,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}}
/**
* Allow the user to search for learners within the grader report.
*
* @module gradereport_grader/user
* @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.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);const selectors_component=".user-search",selectors_courseid='[data-region="courseid"]',courseID=document.querySelector(selectors_component).querySelector(selectors_courseid).dataset.courseid;class User extends _user.default{constructor(){super()}static init(){return new User}fetchDataset(){return Repository.userFetch(courseID).then((r=>r.users))}selectAllResultsLink(){return _url.default.relativeUrl("/grade/report/grader/index.php",{id:courseID,gpr_search:this.getSearchTerm()},!1)}selectOneLink(userID){return _url.default.relativeUrl("/grade/report/grader/index.php",{id:courseID,gpr_search:this.getSearchTerm(),gpr_userid:userID},!1)}}return _exports.default=User,_exports.default}));
//# sourceMappingURL=user.min.js.map
@@ -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\n/**\n * Allow the user to search for learners within the grader report.\n *\n * @module gradereport_grader/user\n * @copyright 2023 Mathew May <mathew.solutions>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport UserSearch from 'core_user/comboboxsearch/user';\nimport Url from 'core/url';\nimport * as Repository from 'gradereport_grader/local/user/repository';\n\n// Define our standard lookups.\nconst selectors = {\n component: '.user-search',\n courseid: '[data-region=\"courseid\"]',\n};\nconst component = document.querySelector(selectors.component);\nconst courseID = component.querySelector(selectors.courseid).dataset.courseid;\n\nexport default class User extends UserSearch {\n\n constructor() {\n super();\n }\n\n static init() {\n return new User();\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(courseID).then((r) => r.users);\n }\n\n /**\n * Build up the view all link.\n *\n * @returns {string|*}\n */\n selectAllResultsLink() {\n return Url.relativeUrl('/grade/report/grader/index.php', {\n id: courseID,\n gpr_search: this.getSearchTerm()\n }, false);\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 return Url.relativeUrl('/grade/report/grader/index.php', {\n id: courseID,\n gpr_search: this.getSearchTerm(),\n gpr_userid: userID,\n }, false);\n }\n}\n"],"names":["selectors","courseID","document","querySelector","dataset","courseid","User","UserSearch","constructor","fetchDataset","Repository","userFetch","then","r","users","selectAllResultsLink","Url","relativeUrl","id","gpr_search","this","getSearchTerm","selectOneLink","userID","gpr_userid"],"mappings":";;;;;;;q0BA2BMA,oBACS,eADTA,mBAEQ,2BAGRC,SADYC,SAASC,cAAcH,qBACdG,cAAcH,oBAAoBI,QAAQC,eAEhDC,aAAaC,cAE9BC,2CAKW,IAAIF,KAQfG,sBACWC,WAAWC,UAAUV,UAAUW,MAAMC,GAAMA,EAAEC,QAQxDC,8BACWC,aAAIC,YAAY,iCAAkC,CACrDC,GAAIjB,SACJkB,WAAYC,KAAKC,kBAClB,GASPC,cAAcC,eACHP,aAAIC,YAAY,iCAAkC,CACrDC,GAAIjB,SACJkB,WAAYC,KAAKC,gBACjBG,WAAYD,SACb"}
+574
View File
@@ -0,0 +1,574 @@
// 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 show and hide columns of the report at will.
*
* @module gradereport_grader/collapse
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as Repository from 'gradereport_grader/collapse/repository';
import search_combobox from 'core/comboboxsearch/search_combobox';
import {renderForPromise, replaceNodeContents, replaceNode} from 'core/templates';
import {debounce} from 'core/utils';
import $ from 'jquery';
import {getStrings} from 'core/str';
import CustomEvents from "core/custom_interaction_events";
import storage from 'core/localstorage';
import {addIconToContainer} from 'core/loadingicon';
import Notification from 'core/notification';
import Pending from 'core/pending';
// Contain our selectors within this file until they could be of use elsewhere.
const selectors = {
component: '.collapse-columns',
formDropdown: '.columnsdropdownform',
formItems: {
cancel: 'cancel',
save: 'save',
checked: 'input[type="checkbox"]:checked',
currentlyUnchecked: 'input[type="checkbox"]:not([data-action="selectall"])',
},
hider: 'hide',
expand: 'expand',
colVal: '[data-col]',
itemVal: '[data-itemid]',
content: '[data-collapse="content"]',
sort: '[data-collapse="sort"]',
expandbutton: '[data-collapse="expandbutton"]',
rangerowcell: '[data-collapse="rangerowcell"]',
avgrowcell: '[data-collapse="avgrowcell"]',
menu: '[data-collapse="menu"]',
icons: '.data-collapse_gradeicons',
count: '[data-collapse="count"]',
placeholder: '.collapsecolumndropdown [data-region="placeholder"]',
fullDropdown: '.collapsecolumndropdown',
searchResultContainer: '.searchresultitemscontainer',
};
const countIndicator = document.querySelector(selectors.count);
export default class ColumnSearch extends search_combobox {
userID = -1;
courseID = null;
defaultSort = '';
nodes = [];
gradeStrings = null;
userStrings = null;
stringMap = [];
static init(userID, courseID, defaultSort) {
return new ColumnSearch(userID, courseID, defaultSort);
}
constructor(userID, courseID, defaultSort) {
super();
this.userID = userID;
this.courseID = courseID;
this.defaultSort = defaultSort;
this.component = document.querySelector(selectors.component);
const pendingPromise = new Pending();
// Display a loader whilst collapsing appropriate columns (based on the locally stored state for the current user).
addIconToContainer(document.querySelector('.gradeparent')).then((loader) => {
setTimeout(() => {
// Get the users' checked columns to change.
this.getDataset().forEach((item) => {
this.nodesUpdate(item);
});
this.renderDefault();
// Once the grade categories have been re-collapsed, remove the loader and display the Gradebook setup content.
loader.remove();
document.querySelector('.gradereport-grader-table').classList.remove('d-none');
}, 10);
}).then(() => pendingPromise.resolve()).catch(Notification.exception);
this.$component.on('hide.bs.dropdown', () => {
const searchResultContainer = this.component.querySelector(selectors.searchResultContainer);
searchResultContainer.scrollTop = 0;
// Use setTimeout to make sure the following code is executed after the click event is handled.
setTimeout(() => {
if (this.searchInput.value !== '') {
this.searchInput.value = '';
this.searchInput.dispatchEvent(new Event('input', {bubbles: true}));
}
});
});
}
/**
* The overall div that contains the searching widget.
*
* @returns {string}
*/
componentSelector() {
return '.collapse-columns';
}
/**
* The dropdown div that contains the searching widget result space.
*
* @returns {string}
*/
dropdownSelector() {
return '.searchresultitemscontainer';
}
/**
* Return the dataset that we will be searching upon.
*
* @returns {Array}
*/
getDataset() {
if (!this.dataset) {
const cols = this.fetchDataset();
this.dataset = JSON.parse(cols) ? JSON.parse(cols).split(',') : [];
}
this.datasetSize = this.dataset.length;
return this.dataset;
}
/**
* Get the data we will be searching against in this component.
*
* @returns {string}
*/
fetchDataset() {
return storage.get(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`);
}
/**
* Given a user performs an action, update the users' preferences.
*/
setPreferences() {
storage.set(`gradereport_grader_collapseditems_${this.courseID}_${this.userID}`,
JSON.stringify(this.getDataset().join(','))
);
}
/**
* Register clickable event listeners.
*/
registerClickHandlers() {
// Register click events within the component.
this.component.addEventListener('click', this.clickHandler.bind(this));
document.addEventListener('click', this.docClickHandler.bind(this));
}
/**
* The handler for when a user interacts with the component.
*
* @param {MouseEvent} e The triggering event that we are working with.
*/
clickHandler(e) {
super.clickHandler(e);
// Prevent BS from closing the dropdown if they click elsewhere within the dropdown besides the form.
if (e.target.closest(selectors.fullDropdown)) {
e.stopPropagation();
}
}
/**
* Externally defined click function to improve memory handling.
*
* @param {MouseEvent} e
* @returns {Promise<void>}
*/
async docClickHandler(e) {
if (e.target.dataset.hider === selectors.hider) {
e.preventDefault();
const desiredToHide = e.target.closest(selectors.colVal) ?
e.target.closest(selectors.colVal)?.dataset.col :
e.target.closest(selectors.itemVal)?.dataset.itemid;
const idx = this.getDataset().indexOf(desiredToHide);
if (idx === -1) {
this.getDataset().push(desiredToHide);
}
await this.prefcountpipe();
this.nodesUpdate(desiredToHide);
}
if (e.target.closest('button')?.dataset.hider === selectors.expand) {
e.preventDefault();
const desiredToHide = e.target.closest(selectors.colVal) ?
e.target.closest(selectors.colVal)?.dataset.col :
e.target.closest(selectors.itemVal)?.dataset.itemid;
const idx = this.getDataset().indexOf(desiredToHide);
this.getDataset().splice(idx, 1);
await this.prefcountpipe();
this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.col);
this.nodesUpdate(e.target.closest(selectors.colVal)?.dataset.itemid);
}
}
/**
* Handle any keyboard inputs.
*/
registerInputEvents() {
// Register & handle the text input.
this.searchInput.addEventListener('input', debounce(async() => {
if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {
window.console.warn(`Search term matches input value - skipping`);
// Debounce can happen multiple times quickly.
return;
}
this.setSearchTerms(this.searchInput.value);
// We can also require a set amount of input before search.
if (this.searchInput.value === '') {
// Hide the "clear" search button in the search bar.
this.clearSearchButton.classList.add('d-none');
} else {
// Display the "clear" search button in the search bar.
this.clearSearchButton.classList.remove('d-none');
}
const pendingPromise = new Pending();
// User has given something for us to filter against.
await this.filterrenderpipe().then(() => {
pendingPromise.resolve();
return true;
});
}, 300, {pending: true}));
}
/**
* Handle the form submission within the dropdown.
*/
registerFormEvents() {
const form = this.component.querySelector(selectors.formDropdown);
const events = [
'click',
CustomEvents.events.activate,
CustomEvents.events.keyboardActivate
];
CustomEvents.define(document, events);
const selectall = form.querySelector('[data-action="selectall"]');
// Register clicks & keyboard form handling.
events.forEach((event) => {
const submitBtn = form.querySelector(`[data-action="${selectors.formItems.save}"`);
form.addEventListener(event, (e) => {
// Stop Bootstrap from being clever.
e.stopPropagation();
const input = e.target.closest('input');
if (input) {
// If the user is unchecking an item, we need to uncheck the select all if it's checked.
if (selectall.checked && !input.checked) {
selectall.checked = false;
}
const checkedCount = Array.from(form.querySelectorAll(selectors.formItems.checked)).length;
// Check if any are clicked or not then change disabled.
submitBtn.disabled = checkedCount <= 0;
}
}, false);
// Stop Bootstrap from being clever.
this.searchInput.addEventListener(event, e => e.stopPropagation());
this.clearSearchButton.addEventListener(event, async(e) => {
e.stopPropagation();
this.searchInput.value = '';
this.setSearchTerms(this.searchInput.value);
await this.filterrenderpipe();
});
selectall.addEventListener(event, (e) => {
// Stop Bootstrap from being clever.
e.stopPropagation();
if (!selectall.checked) {
const touncheck = Array.from(form.querySelectorAll(selectors.formItems.checked));
touncheck.forEach(item => {
item.checked = false;
});
submitBtn.disabled = true;
} else {
const currentUnchecked = Array.from(form.querySelectorAll(selectors.formItems.currentlyUnchecked));
currentUnchecked.forEach(item => {
item.checked = true;
});
submitBtn.disabled = false;
}
});
});
form.addEventListener('submit', async(e) => {
e.preventDefault();
if (e.submitter.dataset.action === selectors.formItems.cancel) {
$(this.component).dropdown('toggle');
return;
}
// Get the users' checked columns to change.
const checkedItems = [...form.elements].filter(item => item.checked);
checkedItems.forEach((item) => {
const idx = this.getDataset().indexOf(item.dataset.collapse);
this.getDataset().splice(idx, 1);
this.nodesUpdate(item.dataset.collapse);
});
// Reset the check all & submit to false just in case.
selectall.checked = false;
e.submitter.disabled = true;
await this.prefcountpipe();
});
}
nodesUpdate(item) {
const colNodesToHide = [...document.querySelectorAll(`[data-col="${item}"]`)];
const itemIDNodesToHide = [...document.querySelectorAll(`[data-itemid="${item}"]`)];
this.nodes = [...colNodesToHide, ...itemIDNodesToHide];
this.updateDisplay();
}
/**
* Update the user preferences, count display then render the results.
*
* @returns {Promise<void>}
*/
async prefcountpipe() {
this.setPreferences();
this.countUpdate();
await this.filterrenderpipe();
}
/**
* Dictate to the search component how and what we want to match upon.
*
* @param {Array} filterableData
* @returns {Array} An array of objects containing the system reference and the user readable value.
*/
async filterDataset(filterableData) {
const stringUserMap = await this.fetchRequiredUserStrings();
const stringGradeMap = await this.fetchRequiredGradeStrings();
// Custom user profile fields are not in our string map and need a bit of extra love.
const customFieldMap = this.fetchCustomFieldValues();
this.stringMap = new Map([...stringGradeMap, ...stringUserMap, ...customFieldMap]);
const searching = filterableData.map(s => {
const mapObj = this.stringMap.get(s);
if (mapObj === undefined) {
return {key: s, string: s};
}
return {
key: s,
string: mapObj.itemname ?? this.stringMap.get(s),
category: mapObj.category ?? '',
};
});
// Sometimes we just want to show everything.
if (this.getPreppedSearchTerm() === '') {
return searching;
}
// Other times we want to actually filter the content.
return searching.filter((col) => {
return col.string.toString().toLowerCase().includes(this.getPreppedSearchTerm());
});
}
/**
* Given we have a subset of the dataset, set the field that we matched upon to inform the end user.
*/
filterMatchDataset() {
this.setMatchedResults(
this.getMatchedResults().map((column) => {
return {
name: column.key,
displayName: column.string ?? column.key,
category: column.category ?? '',
};
})
);
}
/**
* With an array of nodes, switch their classes and values.
*/
updateDisplay() {
this.nodes.forEach((element) => {
const content = element.querySelector(selectors.content);
const sort = element.querySelector(selectors.sort);
const expandButton = element.querySelector(selectors.expandbutton);
const rangeRowCell = element.querySelector(selectors.rangerowcell);
const avgRowCell = element.querySelector(selectors.avgrowcell);
const nodeSet = [
element.querySelector(selectors.menu),
element.querySelector(selectors.icons),
content
];
// This can be further improved to reduce redundant similar calls.
if (element.classList.contains('cell')) {
// The column is actively being sorted, lets reset that and reload the page.
if (sort !== null) {
window.location = this.defaultSort;
}
if (content === null) {
// If it's not a content cell, it must be an overall average or a range cell.
const rowCell = avgRowCell ?? rangeRowCell;
rowCell?.classList.toggle('d-none');
} else if (content.classList.contains('d-none')) {
// We should always have content but some cells do not contain menus or other actions.
element.classList.remove('collapsed');
// If there are many nodes, apply the following.
if (content.childNodes.length > 1) {
content.classList.add('d-flex');
}
nodeSet.forEach(node => {
node?.classList.remove('d-none');
});
expandButton?.classList.add('d-none');
} else {
element.classList.add('collapsed');
content.classList.remove('d-flex');
nodeSet.forEach(node => {
node?.classList.add('d-none');
});
expandButton?.classList.remove('d-none');
}
}
});
}
/**
* Update the visual count of collapsed columns or hide the count all together.
*/
countUpdate() {
countIndicator.textContent = this.getDatasetSize();
if (this.getDatasetSize() > 0) {
this.component.parentElement.classList.add('d-flex');
this.component.parentElement.classList.remove('d-none');
} else {
this.component.parentElement.classList.remove('d-flex');
this.component.parentElement.classList.add('d-none');
}
}
/**
* Build the content then replace the node by default we want our form to exist.
*/
async renderDefault() {
this.setMatchedResults(await this.filterDataset(this.getDataset()));
this.filterMatchDataset();
// Update the collapsed button pill.
this.countUpdate();
const {html, js} = await renderForPromise('gradereport_grader/collapse/collapsebody', {
'instance': this.instance,
'results': this.getMatchedResults(),
'userid': this.userID,
});
replaceNode(selectors.placeholder, html, js);
this.updateNodes();
// Given we now have the body, we can set up more triggers.
this.registerFormEvents();
this.registerInputEvents();
// Add a small BS listener so that we can set the focus correctly on open.
this.$component.on('shown.bs.dropdown', () => {
this.searchInput.focus({preventScroll: true});
this.selectallEnable();
});
}
/**
* Build the content then replace the node.
*/
async renderDropdown() {
const {html, js} = await renderForPromise('gradereport_grader/collapse/collapseresults', {
instance: this.instance,
'results': this.getMatchedResults(),
'searchTerm': this.getSearchTerm(),
});
replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);
this.selectallEnable();
// Reset the expand button to be disabled as we have re-rendered the dropdown.
const form = this.component.querySelector(selectors.formDropdown);
const expandButton = form.querySelector(`[data-action="${selectors.formItems.save}"`);
expandButton.disabled = true;
}
/**
* Given we render the dropdown, Determine if we want to enable the select all checkbox.
*/
selectallEnable() {
const form = this.component.querySelector(selectors.formDropdown);
const selectall = form.querySelector('[data-action="selectall"]');
selectall.disabled = this.getMatchedResults().length === 0;
}
/**
* If we have any custom user profile fields, grab their system & readable names to add to our string map.
*
* @returns {array<string,*>} An array of associated string arrays ready for our map.
*/
fetchCustomFieldValues() {
const customFields = document.querySelectorAll('[data-collapse-name]');
// Cast from NodeList to array to grab all the values.
return [...customFields].map(field => [field.parentElement.dataset.col, field.dataset.collapseName]);
}
/**
* Given the set of profile fields we can possibly search, fetch their strings,
* so we can report to screen readers the field that matched.
*
* @returns {Promise<void>}
*/
fetchRequiredUserStrings() {
if (!this.userStrings) {
const requiredStrings = [
'username',
'firstname',
'lastname',
'email',
'city',
'country',
'department',
'institution',
'idnumber',
'phone1',
'phone2',
];
this.userStrings = getStrings(requiredStrings.map((key) => ({key})))
.then((stringArray) => new Map(
requiredStrings.map((key, index) => ([key, stringArray[index]]))
));
}
return this.userStrings;
}
/**
* Given the set of gradable items we can possibly search, fetch their strings,
* so we can report to screen readers the field that matched.
*
* @returns {Promise<void>}
*/
fetchRequiredGradeStrings() {
if (!this.gradeStrings) {
this.gradeStrings = Repository.gradeItems(this.courseID)
.then((result) => new Map(
result.gradeItems.map(key => ([key.id, key]))
));
}
return this.gradeStrings;
}
}
@@ -0,0 +1,41 @@
// 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 collapsing in the grader report.
*
* @module gradereport_grader/collapse/repository
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import ajax from 'core/ajax';
/**
* Fetch all the information on gradeitems we'll need in the column collapser.
*
* @method gradeItems
* @param {Number} courseid What course to fetch the gradeitems for
* @return {object} jQuery promise
*/
export const gradeItems = (courseid) => {
const request = {
methodname: 'core_grades_get_gradeitems',
args: {
courseid: courseid,
},
};
return ajax.call([request])[0];
};
@@ -0,0 +1,116 @@
// 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/>.
/**
* Javascript module for displaying feedback in a modal window
*
* @module gradereport_grader/feedback_modal
* @copyright 2023 Kevin Percy <kevin.percy@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Modal from 'core/modal';
import Notification from 'core/notification';
import ajax from 'core/ajax';
import Templates from 'core/templates';
const Selectors = {
showFeedback: '[data-action="feedback"]'
};
/**
* Create the modal to display the feedback.
*
* @param {int} courseid
* @param {int} userid
* @param {int} itemid
* @returns {Promise}
*/
const getModal = async(courseid, userid, itemid) => {
let feedbackData;
try {
feedbackData = await fetchFeedback(courseid, userid, itemid);
} catch (e) {
return Promise.reject(e);
}
return Modal.create({
removeOnClose: true,
large: true,
verticallyCentered: true,
})
.then(modal => {
const body = Templates.render('core_grades/feedback_modal', {
feedbacktext: feedbackData.feedbacktext,
user: {
picture: feedbackData.picture,
fullname: feedbackData.fullname,
additionalfield: feedbackData.additionalfield,
},
});
modal.setBody(body);
modal.setTitle(feedbackData.title);
modal.show();
return modal;
});
};
/**
* Fetch the feedback data.
*
* @param {int} courseid
* @param {int} userid
* @param {int} itemid
* @returns {Promise}
*/
const fetchFeedback = (courseid, userid, itemid) => {
const request = {
methodname: 'core_grades_get_feedback',
args: {
courseid: courseid,
userid: userid,
itemid: itemid,
},
};
return ajax.call([request])[0];
};
/**
* Register event listeners for the View Feedback links.
*/
const registerEventListeners = () => {
document.addEventListener('click', e => {
const showFeedbackTrigger = e.target.closest(Selectors.showFeedback);
if (showFeedbackTrigger) {
e.preventDefault();
const courseid = showFeedbackTrigger.dataset.courseid;
const userid = e.target.closest('tr').dataset.uid;
const itemid = e.target.closest('td').dataset.itemid;
getModal(courseid, userid, itemid)
.catch(Notification.exception);
}
});
};
/**
* Initialize module
*/
export const init = () => {
registerEventListeners();
};
+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 grader report.
*
* @module gradereport_grader/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 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/grader/index.php', {
id: this.courseID,
groupsearchvalue: this.getSearchTerm(),
group: groupID,
}, false);
}
}
@@ -0,0 +1,41 @@
// 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 grader report.
*
* @module gradereport_grader/local/user/repository
* @copyright 2022 Mathew May <mathew.solutions>
* @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 report.
*
* @method userFetch
* @param {int} courseid ID of the course to fetch the users of.
* @return {object} jQuery promise
*/
export const userFetch = (courseid) => {
const request = {
methodname: 'gradereport_grader_get_users_in_report',
args: {
courseid: courseid,
},
};
return ajax.call([request])[0];
};
@@ -0,0 +1,104 @@
// 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/>.
/**
* Javascript module for fixing the position of sticky headers with multiple colspans
*
* @module gradereport_grader/stickycolspan
* @copyright 2022 Bas Brands <bas@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import {SELECTORS as stickyFooterSelectors, eventTypes as stickyFooterEvents} from 'core/sticky-footer';
const SELECTORS = {
GRADEPARENT: '.gradeparent',
STUDENTHEADER: '#studentheader',
TABLEHEADER: 'th.header',
BEHAT: 'body.behat-site',
USERDROPDOWN: '.userrow th .dropdown',
LASTROW: '.lastrow',
};
/**
* Initialize module
*/
export const init = () => {
// The sticky positioning attributed to the user column cells affects the stacking context and makes the dropdowns
// within these cells to be cut off. To solve this problem, whenever one of these action menus (dropdowns) is opened
// we need to manually bump up the z-index value of the parent container element and revert once closed.
$(SELECTORS.USERDROPDOWN).on('show.bs.dropdown hide.bs.dropdown', (e) => {
// The closest heading element has sticky positioning which affects the stacking context in this case.
e.target.closest(SELECTORS.TABLEHEADER).classList.toggle('actions-menu-active');
});
defineLastRowIntersectionObserver(true);
// Add an event listener to the sticky footer toggled event to re-define the average row intersection observer
// accordingly. This is needed as on narrow screens when scrolling vertically the sticky footer is enabled and
// disabled dynamically.
document.addEventListener(stickyFooterEvents.stickyFooterStateChanged, (e) => {
defineLastRowIntersectionObserver(e.detail.enabled);
});
if (!document.querySelector(SELECTORS.BEHAT)) {
const grader = document.querySelector(SELECTORS.GRADEPARENT);
const tableHeaders = grader.querySelectorAll(SELECTORS.TABLEHEADER);
const studentHeader = grader.querySelector(SELECTORS.STUDENTHEADER);
const leftOffset = getComputedStyle(studentHeader).getPropertyValue('left');
const rightOffset = getComputedStyle(studentHeader).getPropertyValue('right');
tableHeaders.forEach((tableHeader) => {
if (tableHeader.colSpan > 1) {
const addOffset = (tableHeader.offsetWidth - studentHeader.offsetWidth);
if (window.right_to_left()) {
tableHeader.style.right = 'calc(' + rightOffset + ' - ' + addOffset + 'px )';
} else {
tableHeader.style.left = 'calc(' + leftOffset + ' - ' + addOffset + 'px )';
}
}
});
}
};
/**
* Define the intersection observer that will make sure that the last row is properly pinned.
*
* In certain scenarios, such as when both 'Overall average' and 'Range' are set not to be shown in the Grader report,
* a user row will end up being the last row in the Grader report table. In this particular case, we want to avoid
* pinning the last row.
*
* @param {boolean} stickyFooterEnabled Whether the page shows a sticky footer or not.
*/
const defineLastRowIntersectionObserver = (stickyFooterEnabled) => {
const lastRow = document.querySelector(SELECTORS.LASTROW);
// Ensure that the last row is not a user row before defining the intersection observer.
if (!lastRow.classList.contains('userrow')) {
const stickyFooterHeight = stickyFooterEnabled ?
document.querySelector(stickyFooterSelectors.STICKYFOOTER).offsetHeight : null;
// Register an observer that will bump up the z-index value of the last row when it's pinned to prevent the row
// being cut-off by the user column cells or other components within the report table that have higher z-index
// values. If the page has a sticky footer, we need to make sure that the bottom root margin of the observer
// subtracts the height of the sticky footer to prevent the row being cut-off by the footer.
const intersectionObserver = new IntersectionObserver(
([e]) => lastRow.classList.toggle('pinned', e.intersectionRatio < 1),
{
rootMargin: stickyFooterHeight ? `0px 0px -${stickyFooterHeight}px 0px` : "0px",
threshold: [1]
}
);
intersectionObserver.observe(lastRow.querySelector('th'));
}
};
+79
View File
@@ -0,0 +1,79 @@
// 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 learners within the grader report.
*
* @module gradereport_grader/user
* @copyright 2023 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import UserSearch from 'core_user/comboboxsearch/user';
import Url from 'core/url';
import * as Repository from 'gradereport_grader/local/user/repository';
// Define our standard lookups.
const selectors = {
component: '.user-search',
courseid: '[data-region="courseid"]',
};
const component = document.querySelector(selectors.component);
const courseID = component.querySelector(selectors.courseid).dataset.courseid;
export default class User extends UserSearch {
constructor() {
super();
}
static init() {
return new User();
}
/**
* Get the data we will be searching against in this component.
*
* @returns {Promise<*>}
*/
fetchDataset() {
return Repository.userFetch(courseID).then((r) => r.users);
}
/**
* Build up the view all link.
*
* @returns {string|*}
*/
selectAllResultsLink() {
return Url.relativeUrl('/grade/report/grader/index.php', {
id: courseID,
gpr_search: this.getSearchTerm()
}, false);
}
/**
* Build up the 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/grader/index.php', {
id: courseID,
gpr_search: this.getSearchTerm(),
gpr_userid: userID,
}, false);
}
}
@@ -0,0 +1,47 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grader report viewed event.
*
* @package gradereport_grader
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_grader\event;
defined('MOODLE_INTERNAL') || die();
/**
* Grader report viewed event class.
*
* @package gradereport_grader
* @since Moodle 2.8
* @copyright 2014 Adrian Greeve <adrian@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grade_report_viewed extends \core\event\grade_report_viewed {
/**
* Returns localised general event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventgradereportviewed', 'gradereport_grader');
}
}
@@ -0,0 +1,130 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace gradereport_grader\external;
use context_course;
use core_user_external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use grade_report_grader;
use user_picture;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/course/externallib.php');
require_once($CFG->dirroot .'/user/externallib.php');
require_once($CFG->dirroot.'/grade/lib.php');
require_once($CFG->dirroot.'/grade/report/grader/lib.php');
/**
* External grade report grader API
*
* @package gradereport_grader
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_users_in_report extends external_api {
/**
* Describes the parameters for get_users_in_report
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters (
[
'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED)
]
);
}
/**
* Given a course ID find Fetch the grader report and add some fields to the returned users.
*
* @param int $courseid Course ID to fetch the grader report for.
* @return array Users and warnings to pass back to the calling widget.
*/
public static function execute(int $courseid): array {
global $PAGE;
self::validate_parameters(
self::execute_parameters(),
[
'courseid' => $courseid,
]
);
$warnings = [];
$context = context_course::instance($courseid);
self::validate_context($context);
require_capability('gradereport/grader:view', $context);
// Return tracking object.
$gpr = new \grade_plugin_return(
[
'type' => 'report',
'plugin' => 'grader',
'courseid' => $courseid
]
);
$report = new grade_report_grader($courseid, $gpr, $context);
$userfieldsapi = \core_user\fields::for_identity($context, false)->with_userpic();
$extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
// For the returned users, Add a couple of extra fields that we need for the search module.
$users = array_map(function ($user) use ($PAGE, $extrauserfields) {
$userforselector = new \stdClass();
$userforselector->id = $user->id;
$userforselector->fullname = fullname($user);
foreach (\core_user\fields::get_name_fields() as $field) {
$userforselector->$field = $user->$field ?? null;
}
$userpicture = new user_picture($user);
$userpicture->size = 1;
$userforselector->profileimageurl = $userpicture->get_url($PAGE)->out(false);
$userpicture->size = 0; // Size f2.
$userforselector->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
foreach ($extrauserfields as $field) {
$userforselector->$field = $user->$field ?? null;
}
return $userforselector;
}, $report->load_users(true));
sort($users);
return [
'users' => $users,
'warnings' => $warnings,
];
}
/**
* Returns description of what the users & warnings should return.
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'users' => new external_multiple_structure(core_user_external::user_description()),
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,184 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace gradereport_grader\output;
use core\output\comboboxsearch;
use core_grades\output\general_action_bar;
use moodle_url;
/**
* Renderable class for the action bar elements in the grader report.
*
* @package gradereport_grader
* @copyright 2022 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class action_bar extends \core_grades\output\action_bar {
/** @var string $usersearch The content that the current user is looking for. */
protected string $usersearch = '';
/** @var int $userid The ID of the user that the current user is looking for. */
protected int $userid = 0;
/**
* The class constructor.
*
* @param \context_course $context The context object.
*/
public function __construct(\context_course $context) {
parent::__construct($context);
$this->userid = optional_param('gpr_userid', 0, PARAM_INT);
$this->usersearch = optional_param('gpr_search', '', PARAM_NOTAGS);
if ($this->userid) {
$user = \core_user::get_user($this->userid);
$this->usersearch = fullname($user);
}
}
/**
* Returns the template for the action bar.
*
* @return string
*/
public function get_template(): string {
return 'gradereport_grader/action_bar';
}
/**
* Export the data for the mustache template.
*
* @param \renderer_base $output renderer to be used to render the action bar elements.
* @return array
* @throws \moodle_exception
*/
public function export_for_template(\renderer_base $output): array {
global $PAGE, $OUTPUT, $SESSION, $USER;
// If in the course context, we should display the general navigation selector in gradebook.
$courseid = $this->context->instanceid;
// Get the data used to output the general navigation selector.
$generalnavselector = new general_action_bar($this->context,
new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]), 'gradereport', 'grader');
$data = $generalnavselector->export_for_template($output);
// 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');
$initialscontent = $gradesrenderer->initials_selector(
$course,
$this->context,
'/grade/report/grader/index.php'
);
$firstnameinitial = $SESSION->gradereport["filterfirstname-{$this->context->id}"] ?? '';
$lastnameinitial = $SESSION->gradereport["filtersurname-{$this->context->id}"] ?? '';
$initialselector = new comboboxsearch(
false,
$initialscontent->buttoncontent,
$initialscontent->dropdowncontent,
'initials-selector',
'initialswidget',
'initialsdropdown',
$initialscontent->buttonheader,
true,
get_string('filterbyname', 'core_grades'),
'nameinitials',
json_encode([
'first' => $firstnameinitial,
'last' => $lastnameinitial,
])
);
$data['initialselector'] = $initialselector->export_for_template($output);
$data['groupselector'] = $gradesrenderer->group_selector($course);
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);
$searchinput = $OUTPUT->render_from_template('core_user/comboboxsearch/user_selector', [
'currentvalue' => $this->usersearch,
'courseid' => $courseid,
'instance' => rand(),
'resetlink' => $resetlink->out(false),
'group' => 0,
'name' => 'usersearch',
'value' => json_encode([
'userid' => $this->userid,
'search' => $this->usersearch,
]),
]);
$searchdropdown = new comboboxsearch(
true,
$searchinput,
null,
'user-search d-flex',
null,
'usersearchdropdown overflow-auto',
null,
false,
);
$data['searchdropdown'] = $searchdropdown->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.
$collapsemenudirection = right_to_left() ? 'dropdown-menu-left' : 'dropdown-menu-right';
$collapse = new comboboxsearch(
true,
get_string('collapsedcolumns', 'gradereport_grader', 0),
null,
'collapse-columns',
'collapsecolumn',
'collapsecolumndropdown p-3 flex-column ' . $collapsemenudirection,
null,
true,
get_string('aria:dropdowncolumns', 'gradereport_grader'),
'collapsedcolumns'
);
$data['collapsedcolumns'] = [
'classes' => 'd-none',
'content' => $collapse->export_for_template($output)
];
if ($course->groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $this->context)) {
$allowedgroups = groups_get_all_groups($course->id, 0, $course->defaultgroupingid);
} else {
$allowedgroups = groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid);
}
if (
$firstnameinitial ||
$lastnameinitial ||
groups_get_course_group($course, true, $allowedgroups) ||
$this->usersearch
) {
$reset = new moodle_url('/grade/report/grader/index.php', [
'id' => $courseid,
'group' => 0,
'sifirst' => '',
'silast' => ''
]);
$data['pagereset'] = $reset->out(false);
}
}
return $data;
}
}
@@ -0,0 +1,170 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for gradereport_grader.
*
* @package gradereport_grader
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_grader\privacy;
defined('MOODLE_INTERNAL') || die();
use \core_privacy\local\metadata\collection;
use \core_privacy\local\request\transform;
use \core_privacy\local\request\writer;
require_once $CFG->libdir.'/grade/constants.php';
/**
* Privacy Subsystem for gradereport_grader implementing null_provider.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $itemcollection The initialised item collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $items): collection {
// There are several user preferences (shared between different courses).
// Show/hide toggles preferences.
$items->add_user_preference('grade_report_showaverages', 'privacy:metadata:preference:grade_report_showaverages');
$items->add_user_preference('grade_report_showuserimage', 'privacy:metadata:preference:grade_report_showuserimage');
$items->add_user_preference('grade_report_showranges', 'privacy:metadata:preference:grade_report_showranges');
// Special rows preferences.
$items->add_user_preference('grade_report_rangesdisplaytype', 'privacy:metadata:preference:grade_report_rangesdisplaytype');
$items->add_user_preference('grade_report_rangesdecimalpoints', 'privacy:metadata:preference:grade_report_rangesdecimalpoints');
$items->add_user_preference('grade_report_averagesdisplaytype', 'privacy:metadata:preference:grade_report_averagesdisplaytype');
$items->add_user_preference('grade_report_averagesdecimalpoints', 'privacy:metadata:preference:grade_report_averagesdecimalpoints');
$items->add_user_preference('grade_report_meanselection', 'privacy:metadata:preference:grade_report_meanselection');
$items->add_user_preference('grade_report_shownumberofgrades', 'privacy:metadata:preference:grade_report_shownumberofgrades');
// General preferences.
$items->add_user_preference('grade_report_quickgrading', 'privacy:metadata:preference:grade_report_quickgrading');
$items->add_user_preference('grade_report_studentsperpage', 'privacy:metadata:preference:grade_report_studentsperpage');
$items->add_user_preference('grade_report_showonlyactiveenrol', 'privacy:metadata:preference:grade_report_showonlyactiveenrol');
$items->add_user_preference('grade_report_aggregationposition', 'privacy:metadata:preference:grade_report_aggregationposition');
// There is also one user preference which can be defined on each course.
$items->add_user_preference('grade_report_grader_collapsed_categories', 'privacy:metadata:preference:grade_report_grader_collapsed_categories');
return $items;
}
/**
* Store all user preferences for the plugin.
*
* @param int $userid The userid of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preferences = get_user_preferences(null, null, $userid);
foreach ($preferences as $name => $value) {
$prefname = null;
$prefdescription = null;
$transformedvalue = null;
switch ($name) {
case 'grade_report_showaverages':
case 'grade_report_showuserimage':
case 'grade_report_showranges':
case 'grade_report_shownumberofgrades':
case 'grade_report_quickgrading':
case 'grade_report_showonlyactiveenrol':
case 'grade_report_meanselection':
$prefname = $name;
switch ($value) {
case GRADE_REPORT_MEAN_ALL:
$transformedvalue = get_string('meanall', 'grades');
break;
case GRADE_REPORT_MEAN_GRADED:
$transformedvalue = get_string('meangraded', 'grades');
break;
}
break;
case 'grade_report_rangesdecimalpoints':
case 'grade_report_averagesdecimalpoints':
case 'grade_report_studentsperpage':
$prefname = $name;
$transformedvalue = $value;
break;
case 'grade_report_rangesdisplaytype':
case 'grade_report_averagesdisplaytype':
$prefname = $name;
switch ($value) {
case GRADE_REPORT_PREFERENCE_INHERIT:
$transformedvalue = get_string('inherit', 'grades');
break;
case GRADE_DISPLAY_TYPE_REAL:
$transformedvalue = get_string('real', 'grades');
break;
case GRADE_DISPLAY_TYPE_PERCENTAGE:
$transformedvalue = get_string('percentage', 'grades');
break;
case GRADE_DISPLAY_TYPE_LETTER:
$transformedvalue = get_string('letter', 'grades');
break;
}
break;
case 'grade_report_aggregationposition':
$prefname = $name;
switch ($value) {
case GRADE_REPORT_AGGREGATION_POSITION_FIRST:
$transformedvalue = get_string('positionfirst', 'grades');
break;
case GRADE_REPORT_AGGREGATION_POSITION_LAST:
$transformedvalue = get_string('positionlast', 'grades');
break;
}
break;
default:
if (strpos($name, 'grade_report_grader_collapsed_categories') === 0) {
$prefname = 'grade_report_grader_collapsed_categories';
$courseid = substr($name, strlen('grade_report_grader_collapsed_categories'));
$transformedvalue = $value;
$course = get_course($courseid);
$prefdescription = get_string(
'privacy:request:preference:'.$prefname,
'gradereport_grader',
(object) [
'name' => $course->fullname,
]
);
}
}
if ($prefname !== null) {
if ($prefdescription == null) {
$prefdescription = get_string('privacy:metadata:preference:'.$prefname, 'gradereport_grader');
}
writer::export_user_preference(
'gradereport_grader',
$prefname,
$transformedvalue,
$prefdescription
);
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Capability definition for the gradebook grader report
*
* @package gradereport_grader
* @copyright 2007 Moodle Pty Ltd (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'gradereport/grader:view' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
)
);
+37
View File
@@ -0,0 +1,37 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grader grade report external functions and service definitions.
*
* @package gradereport_grader
* @copyright 2022 Mathew May <Mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$functions = [
'gradereport_grader_get_users_in_report' => [
'classname' => 'gradereport_grader\\external\\get_users_in_report',
'methodname' => 'execute',
'description' => 'Returns the dataset of users within the report',
'type' => 'read',
'ajax' => true,
'capabilities' => 'gradereport/grader:view',
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
],
];
+84
View File
@@ -0,0 +1,84 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grader report upgrade steps.
*
* @package gradereport_grader
* @copyright 2023 Ilya Tregubov <ilya@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Function to upgrade grader report.
*
* @param int $oldversion the version we are upgrading from
* @return bool result
*/
function xmldb_gradereport_grader_upgrade(int $oldversion): bool {
global $DB;
if ($oldversion < 2023032100) {
// Remove grade_report_showquickfeedback, grade_report_enableajax, grade_report_showeyecons,
// grade_report_showlocks, grade_report_showanalysisicon preferences for every user.
$DB->delete_records('user_preferences', ['name' => 'grade_report_showquickfeedback']);
$DB->delete_records('user_preferences', ['name' => 'grade_report_enableajax']);
$DB->delete_records('user_preferences', ['name' => 'grade_report_showeyecons']);
$DB->delete_records('user_preferences', ['name' => 'grade_report_showlocks']);
$DB->delete_records('user_preferences', ['name' => 'grade_report_showanalysisicon']);
// Remove grade_report_showactivityicons, grade_report_showcalculations preferences for every user.
$DB->delete_records('user_preferences', ['name' => 'grade_report_showactivityicons']);
$DB->delete_records('user_preferences', ['name' => 'grade_report_showcalculations']);
// The grade_report_showquickfeedback, grade_report_enableajax, grade_report_showeyecons,
// grade_report_showlocks, grade_report_showanalysisicon settings have been removed.
unset_config('grade_report_showquickfeedback');
unset_config('grade_report_enableajax');
unset_config('grade_report_showeyecons');
unset_config('grade_report_showlocks');
unset_config('grade_report_showanalysisicon');
// The grade_report_showactivityicons, grade_report_showcalculations settings have been removed.
unset_config('grade_report_showactivityicons');
unset_config('grade_report_showcalculations');
// Main savepoint reached.
upgrade_plugin_savepoint(true, 2023032100, 'gradereport', 'grader');
}
if ($oldversion < 2023032700) {
unset_config('grade_report_studentsperpage');
upgrade_plugin_savepoint(true, 2023032700, 'gradereport', 'grader');
}
if ($oldversion < 2023032800) {
// Remove plugin entry created by previously incorrect 2023032100 savepoint.
$DB->delete_records('config_plugins', ['plugin' => 'grade_gradereport_grader']);
upgrade_plugin_savepoint(true, 2023032800, 'gradereport', 'grader');
}
// Automatically generated Moodle v4.2.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.3.0 release upgrade line.
// Put any upgrade step following this.
// Automatically generated Moodle v4.4.0 release upgrade line.
// Put any upgrade step following this.
return true;
}
+265
View File
@@ -0,0 +1,265 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* The gradebook grader report
*
* @package gradereport_grader
* @copyright 2007 Moodle Pty Ltd (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../../../config.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/user/renderer.php');
require_once($CFG->dirroot.'/grade/lib.php');
require_once($CFG->dirroot.'/grade/report/grader/lib.php');
// This report may require a lot of memory and time on large courses.
raise_memory_limit(MEMORY_HUGE);
set_time_limit(120);
$courseid = required_param('id', PARAM_INT); // course id
$page = optional_param('page', 0, PARAM_INT); // active page
$edit = optional_param('edit', -1, PARAM_BOOL); // sticky editting mode
$sortitemid = optional_param('sortitemid', 0, PARAM_ALPHANUMEXT);
$sort = optional_param('sort', '', PARAM_ALPHA);
$action = optional_param('action', 0, PARAM_ALPHAEXT);
$move = optional_param('move', 0, PARAM_INT);
$type = optional_param('type', 0, PARAM_ALPHA);
$target = optional_param('target', 0, PARAM_ALPHANUM);
$toggle = optional_param('toggle', null, PARAM_INT);
$toggle_type = optional_param('toggle_type', 0, PARAM_ALPHANUM);
$graderreportsifirst = optional_param('sifirst', null, PARAM_NOTAGS);
$graderreportsilast = optional_param('silast', null, PARAM_NOTAGS);
$studentsperpage = optional_param('perpage', null, PARAM_INT);
$PAGE->set_url(new moodle_url('/grade/report/grader/index.php', array('id'=>$courseid)));
$PAGE->set_pagelayout('report');
$PAGE->requires->js_call_amd('gradereport_grader/stickycolspan', 'init');
$PAGE->requires->js_call_amd('gradereport_grader/user', 'init');
$PAGE->requires->js_call_amd('gradereport_grader/feedback_modal', 'init');
$PAGE->requires->js_call_amd('core_grades/gradebooksetup_forms', 'init');
// basic access checks
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
throw new \moodle_exception('invalidcourseid');
}
// Conditionally add the group JS if we have groups enabled.
if ($course->groupmode) {
$PAGE->requires->js_call_amd('gradereport_grader/group', 'init');
}
require_login($course);
$context = context_course::instance($course->id);
// The report object is recreated each time, save search information to SESSION object for future use.
if (isset($graderreportsifirst)) {
$SESSION->gradereport["filterfirstname-{$context->id}"] = $graderreportsifirst;
}
if (isset($graderreportsilast)) {
$SESSION->gradereport["filtersurname-{$context->id}"] = $graderreportsilast;
}
if (isset($studentsperpage) && $studentsperpage >= 0) {
set_user_preference('grade_report_studentsperpage', $studentsperpage);
}
require_capability('gradereport/grader:view', $context);
require_capability('moodle/grade:viewall', $context);
// return tracking object
$gpr = new grade_plugin_return(
array(
'type' => 'report',
'plugin' => 'grader',
'course' => $course,
'page' => $page
)
);
// last selected report session tracking
if (!isset($USER->grade_last_report)) {
$USER->grade_last_report = array();
}
$USER->grade_last_report[$course->id] = 'grader';
// Build editing on/off buttons.
$buttons = '';
$PAGE->set_other_editing_capability('moodle/grade:edit');
if ($PAGE->user_allowed_editing() && !$PAGE->theme->haseditswitch) {
if ($edit != - 1) {
$USER->editing = $edit;
}
// Page params for the turn editing on button.
$options = $gpr->get_options();
$buttons = $OUTPUT->edit_button(new moodle_url($PAGE->url, $options), 'get');
}
$gradeserror = array();
// Handle toggle change request
if (!is_null($toggle) && !empty($toggle_type)) {
set_user_preferences(array('grade_report_show'.$toggle_type => $toggle));
}
// Perform actions
if (!empty($target) && !empty($action) && confirm_sesskey()) {
grade_report_grader::do_process_action($target, $action, $courseid);
}
$reportname = get_string('pluginname', 'gradereport_grader');
// Do this check just before printing the grade header (and only do it once).
grade_regrade_final_grades_if_required($course);
//Initialise the grader report object that produces the table
//the class grade_report_grader_ajax was removed as part of MDL-21562
if ($sort && strcasecmp($sort, 'desc') !== 0) {
$sort = 'asc';
}
// We have lots of hardcoded 'ASC' and 'DESC' strings in grade/report/grader.lib :(. So we need to uppercase the sort.
$sort = strtoupper($sort);
$report = new grade_report_grader($courseid, $gpr, $context, $page, $sortitemid, $sort);
// We call this a little later since we need some info from the grader report.
$PAGE->requires->js_call_amd('gradereport_grader/collapse', 'init', [
'userID' => $USER->id,
'courseID' => $courseid,
'defaultSort' => $report->get_default_sortable()
]);
$numusers = $report->get_numusers(true, true);
$actionbar = new \gradereport_grader\output\action_bar($context, $report, $numusers);
print_grade_page_head($COURSE->id, 'report', 'grader', false, false, $buttons, true,
null, null, null, $actionbar);
// make sure separate group does not prevent view
if ($report->currentgroup == -2) {
echo $OUTPUT->heading(get_string("notingroup"));
echo $OUTPUT->footer();
exit;
}
$warnings = [];
$isediting = has_capability('moodle/grade:edit', $context) && isset($USER->editing) && $USER->editing;
if ($isediting && ($data = data_submitted()) && confirm_sesskey()) {
// Processing posted grades here.
$warnings = $report->process_data($data);
}
// Final grades MUST be loaded after the processing.
$report->load_users();
$report->load_final_grades();
//show warnings if any
foreach ($warnings as $warning) {
echo $OUTPUT->notification($warning);
}
$displayaverages = true;
if ($numusers == 0) {
$displayaverages = false;
}
$reporthtml = $report->get_grade_table($displayaverages);
$studentsperpage = $report->get_students_per_page();
// Print per-page dropdown.
$pagingoptions = grade_report_grader::PAGINATION_OPTIONS;
if ($studentsperpage) {
$pagingoptions[] = $studentsperpage; // To make sure the current preference is within the options.
}
$pagingoptions = array_unique($pagingoptions);
sort($pagingoptions);
$pagingoptions = array_combine($pagingoptions, $pagingoptions);
$maxusers = $report->get_max_students_per_page();
if ($numusers > $maxusers) {
$pagingoptions['0'] = $maxusers;
} else {
$pagingoptions['0'] = get_string('all');
}
$perpagedata = [
'baseurl' => new moodle_url('/grade/report/grader/index.php', ['id' => s($courseid), 'report' => 'grader']),
'options' => []
];
foreach ($pagingoptions as $key => $name) {
$perpagedata['options'][] = [
'name' => $name,
'value' => $key,
'selected' => $key == $studentsperpage,
];
}
$footercontent = html_writer::div(
$OUTPUT->render_from_template('gradereport_grader/perpage', $perpagedata)
, 'col-auto'
);
// The number of students per page is always limited even if it is claimed to be unlimited.
$studentsperpage = $studentsperpage ?: $maxusers;
$footercontent .= html_writer::div(
$OUTPUT->paging_bar($numusers, $report->page, $studentsperpage, $report->pbarurl),
'col'
);
// print submit button
if (!empty($USER->editing) && $report->get_pref('quickgrading')) {
echo '<form action="index.php" enctype="application/x-www-form-urlencoded" method="post" id="gradereport_grader">'; // Enforce compatibility with our max_input_vars hack.
echo '<div>';
echo '<input type="hidden" value="'.s($courseid).'" name="id" />';
echo '<input type="hidden" value="'.sesskey().'" name="sesskey" />';
echo '<input type="hidden" value="'.time().'" name="timepageload" />';
echo '<input type="hidden" value="grader" name="report"/>';
echo '<input type="hidden" value="'.$page.'" name="page"/>';
echo $gpr->get_form_fields();
echo $reporthtml;
$footercontent .= html_writer::div(
'<input type="submit" id="gradersubmit" class="btn btn-primary" value="'.s(get_string('savechanges')).'" />',
'col-auto'
);
$stickyfooter = new core\output\sticky_footer($footercontent);
echo $OUTPUT->render($stickyfooter);
echo '</div></form>';
} else {
echo $reporthtml;
$stickyfooter = new core\output\sticky_footer($footercontent);
echo $OUTPUT->render($stickyfooter);
}
$event = \gradereport_grader\event\grade_report_viewed::create(
array(
'context' => $context,
'courseid' => $courseid,
)
);
$event->trigger();
echo $OUTPUT->footer();
@@ -0,0 +1,13 @@
privacy:metadata:preference:grade_report_showquickfeedback,gradereport_grader
useractivityfeedback,gradereport_grader
privacy:metadata:preference:grade_report_enableajax,gradereport_grader
ajaxclicktoclose,gradereport_grader
ajaxerror,gradereport_grader
ajaxfailedupdate,gradereport_grader
ajaxfieldchanged,gradereport_grader
ajaxchoosescale,gradereport_grader
privacy:metadata:preference:grade_report_showeyecons,gradereport_grader
privacy:metadata:preference:grade_report_showlocks,gradereport_grader
privacy:metadata:preference:grade_report_showanalysisicon,gradereport_grader
privacy:metadata:preference:grade_report_showactivityicons,gradereport_grader
privacy:metadata:preference:grade_report_showcalculations,gradereport_grader
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Strings for component 'gradereport_grader', language 'en'
*
* @package gradereport_grader
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['aria:dropdowncolumns'] = 'Collapsed columns found';
$string['clearall'] = 'Clear all';
$string['clearsearch'] = 'Clear searched users';
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge rounded-pill bg-primary text-white ml-1" data-collapse="count">{$a}</span>';
$string['editingmode_title'] = 'Editing the grader report';
$string['eventgradereportviewed'] = 'Grader report viewed';
$string['grader:manage'] = 'Manage the grader report';
$string['grader:view'] = 'View grader report';
$string['pluginname'] = 'Grader report';
$string['preferences'] = 'Grader report preferences';
$string['privacy:metadata:preference:grade_report_aggregationposition'] = 'Whether the category and course total columns are displayed first or last in the gradebook reports';
$string['privacy:metadata:preference:grade_report_averagesdecimalpoints'] = 'The number of decimal places to display for each average or whether the overall decimal places setting for the category or grade item is used (inherit).';
$string['privacy:metadata:preference:grade_report_averagesdisplaytype'] = 'Whether the average (mean) is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit).';
$string['privacy:metadata:preference:grade_report_grader_collapsed_categories'] = 'List of gradebook categories to be collapsed';
$string['privacy:metadata:preference:grade_report_meanselection'] = 'Whether cells with no grade should be included when calculating the average (mean) for each category or grade item';
$string['privacy:metadata:preference:grade_report_quickgrading'] = 'Whether to display a text input box for each grade, allowing many grades to be edited at the same time';
$string['privacy:metadata:preference:grade_report_rangesdecimalpoints'] = 'The number of decimal places to display for each range or whether the overall decimal places setting for the category or grade item is used (inherit)';
$string['privacy:metadata:preference:grade_report_rangesdisplaytype'] = 'Whether the range is displayed as real grades, percentages or letters, or whether the display type for the category or grade item is used (inherit)';
$string['privacy:metadata:preference:grade_report_showaverages'] = 'Whether to display an additional row with the average (mean) for each category and grade item';
$string['privacy:metadata:preference:grade_report_shownumberofgrades'] = 'Whether to display in brackets after each average the number of grades used when calculating the average';
$string['privacy:metadata:preference:grade_report_showonlyactiveenrol'] = 'Whether to display only active enrolled users (not suspended) in gradebook report or all the enrolled users';
$string['privacy:metadata:preference:grade_report_showranges'] = 'Whether to display an additional row with the range for each category and grade item';
$string['privacy:metadata:preference:grade_report_showuserimage'] = 'Whether to show the user\'s profile image next to the name';
$string['privacy:metadata:preference:grade_report_studentsperpage'] = 'The number of students displayed per page in the grader report';
$string['privacy:request:preference:grade_report_grader_collapsed_categories'] = 'You have some gradebook categories collapsed in the "{$a->name}" course';
$string['reopencolumn'] = 'Expand column {$a}';
$string['summarygrader'] = 'A table with the names of students in the first column, with assessable activities grouped by course and category across the top.';
$string['useractivitygrade'] = '{$a} grade';
$string['overriddengrade'] = 'Overridden grade';
$string['advancedgrading'] = 'View {$a} results';
$string['cellactions'] = 'Cell actions';
// Deprecated since Moodle 4.2.
$string['privacy:metadata:preference:grade_report_showanalysisicon'] = 'Whether to show grade analysis icon by default. If the activity module supports it, the grade analysis icon links to a page with more detailed explanation of the grade and how it was obtained.';
$string['privacy:metadata:preference:grade_report_showeyecons'] = 'Whether to display the show/hide icon for each grade for controlling its visibility to the student';
$string['privacy:metadata:preference:grade_report_showlocks'] = 'Whether to display a lock/unlock icon for each grade for controlling if the grade can be automatically updated by the related activity when editing is turned on';
$string['privacy:metadata:preference:grade_report_showquickfeedback'] = 'Whether to display a feedback text input box with a dotted border for each grade, allowing the feedback for many grades to be edited at the same time';
$string['privacy:metadata:preference:grade_report_enableajax'] = 'Whether to add a layer of AJAX functionality to the grader report, simplifying and speeding up common operations';
$string['useractivityfeedback'] = '{$a} feedback';
$string['ajaxclicktoclose'] = 'Click this box to remove it';
$string['ajaxerror'] = 'Error';
$string['ajaxfailedupdate'] = 'Unable to update [1] for [2]';
$string['ajaxfieldchanged'] = 'The field you are currently editing has changed, would you like to use the updated value?';
$string['ajaxchoosescale'] = 'Choose';
$string['privacy:metadata:preference:grade_report_showactivityicons'] = 'Whether to show the activity icons next to activity names';
$string['privacy:metadata:preference:grade_report_showcalculations'] = 'Whether a calculator icon for each grade item and category with a visual indicator that a grade item is calculated is displayed or not when editing is turned on';
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Grader report preferences configuration page
*
* @package gradereport_grader
* @copyright 2007 Nicolas Connault
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once '../../../config.php';
require_once $CFG->libdir . '/gradelib.php';
require_once '../../lib.php';
core_php_time_limit::raise();
$courseid = required_param('id', PARAM_INT);
$PAGE->set_url(new moodle_url('/grade/report/grader/preferences.php', array('id'=>$courseid)));
$PAGE->set_pagelayout('admin');
/// Make sure they can even access this course
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
throw new \moodle_exception('invalidcourseid');
}
require_login($course);
$context = context_course::instance($course->id);
$systemcontext = context_system::instance();
require_capability('gradereport/grader:view', $context);
require('preferences_form.php');
$mform = new grader_report_preferences_form('preferences.php', compact('course'));
// If data submitted, then process and store.
if ($data = $mform->get_data()) {
foreach ($data as $preference => $value) {
if (substr($preference, 0, 6) !== 'grade_') {
continue;
}
if ($value == GRADE_REPORT_PREFERENCE_DEFAULT || strlen($value) == 0) {
unset_user_preference($preference);
} else {
set_user_preference($preference, $value);
}
}
}
print_grade_page_head($courseid, 'settings', 'grader');
// If USER has admin capability, print a link to the site config page for this report
if (has_capability('moodle/site:config', $systemcontext)) {
echo '<div id="siteconfiglink"><a href="'.$CFG->wwwroot.'/'.$CFG->admin.'/settings.php?section=gradereportgrader">';
echo get_string('changereportdefaults', 'grades');
echo "</a></div>\n";
}
echo $OUTPUT->box_start();
$mform->display();
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
+190
View File
@@ -0,0 +1,190 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Form for grader report preferences
*
* @package gradereport_grader
* @copyright 2009 Nicolas Connault
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
require_once($CFG->libdir.'/formslib.php');
/**
* First implementation of the preferences in the form of a moodleform.
* TODO add "reset to site defaults" button
*/
class grader_report_preferences_form extends moodleform {
function definition() {
global $USER, $CFG;
$mform =& $this->_form;
$course = $this->_customdata['course'];
$context = context_course::instance($course->id);
$canviewhidden = has_capability('moodle/grade:viewhidden', $context);
$checkbox_default = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*', 0 => get_string('no'), 1 => get_string('yes'));
$advanced = array();
/// form definition with preferences defaults
//--------------------------------------------------------------------------------
$preferences = array();
// Initialise the preferences arrays with grade:manage capabilities
if (has_capability('moodle/grade:manage', $context)) {
$preferences['prefshow'] = array();
if ($canviewhidden) {
$preferences['prefshow']['showaverages'] = $checkbox_default;
}
$preferences['prefrows'] = array(
'rangesdisplaytype' => array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_PREFERENCE_INHERIT => get_string('inherit', 'grades'),
GRADE_DISPLAY_TYPE_REAL => get_string('real', 'grades'),
GRADE_DISPLAY_TYPE_PERCENTAGE => get_string('percentage', 'grades'),
GRADE_DISPLAY_TYPE_LETTER => get_string('letter', 'grades')),
'rangesdecimalpoints' => array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_PREFERENCE_INHERIT => get_string('inherit', 'grades'),
0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5));
$advanced = array_merge($advanced, array('rangesdisplaytype', 'rangesdecimalpoints'));
if ($canviewhidden) {
$preferences['prefrows']['averagesdisplaytype'] = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_PREFERENCE_INHERIT => get_string('inherit', 'grades'),
GRADE_DISPLAY_TYPE_REAL => get_string('real', 'grades'),
GRADE_DISPLAY_TYPE_PERCENTAGE => get_string('percentage', 'grades'),
GRADE_DISPLAY_TYPE_LETTER => get_string('letter', 'grades'));
$preferences['prefrows']['averagesdecimalpoints'] = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_PREFERENCE_INHERIT => get_string('inherit', 'grades'),
0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5);
$preferences['prefrows']['meanselection'] = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_MEAN_ALL => get_string('meanall', 'grades'),
GRADE_REPORT_MEAN_GRADED => get_string('meangraded', 'grades'));
$advanced = array_merge($advanced, array('averagesdisplaytype', 'averagesdecimalpoints'));
}
}
// Quickgrading use conditional on grade:edit capability.
if (has_capability('moodle/grade:edit', $context)) {
$preferences['prefgeneral']['quickgrading'] = $checkbox_default;
}
// View capability is the lowest permission. Users with grade:manage or grade:edit must also have grader:view
if (has_capability('gradereport/grader:view', $context)) {
if (has_capability('moodle/course:viewsuspendedusers', $context)) {
$preferences['prefgeneral']['showonlyactiveenrol'] = $checkbox_default;
}
$preferences['prefgeneral']['aggregationposition'] = array(GRADE_REPORT_PREFERENCE_DEFAULT => '*default*',
GRADE_REPORT_AGGREGATION_POSITION_FIRST => get_string('positionfirst', 'grades'),
GRADE_REPORT_AGGREGATION_POSITION_LAST => get_string('positionlast', 'grades'));
$preferences['prefshow']['showuserimage'] = $checkbox_default;
$preferences['prefshow']['showranges'] = $checkbox_default;
if ($canviewhidden) {
$preferences['prefrows']['shownumberofgrades'] = $checkbox_default;
}
}
foreach ($preferences as $group => $prefs) {
$mform->addElement('header', $group, get_string($group, 'grades'));
$mform->setExpanded($group);
foreach ($prefs as $pref => $type) {
// Detect and process dynamically numbered preferences
if (preg_match('/([^[0-9]+)([0-9]+)/', $pref, $matches)) {
$lang_string = $matches[1];
$number = ' ' . $matches[2];
} else {
$lang_string = $pref;
$number = null;
}
$full_pref = 'grade_report_' . $pref;
$pref_value = get_user_preferences($full_pref);
$options = null;
if (is_array($type)) {
$options = $type;
$type = 'select';
// MDL-11478
// get default aggregationposition from grade_settings
$course_value = null;
if (!empty($CFG->{$full_pref})) {
$course_value = grade_get_setting($course->id, $pref, $CFG->{$full_pref});
}
if ($pref == 'aggregationposition') {
if (!empty($options[$course_value])) {
$default = $options[$course_value];
} else {
$default = $options[$CFG->grade_aggregationposition];
}
} elseif (isset($options[$CFG->{$full_pref}])) {
$default = $options[$CFG->{$full_pref}];
} else {
$default = '';
}
} else {
$default = $CFG->$full_pref;
}
// Replace the '*default*' value with the site default language string - 'default' might collide with custom language packs
if (!is_null($options) AND isset($options[GRADE_REPORT_PREFERENCE_DEFAULT]) && $options[GRADE_REPORT_PREFERENCE_DEFAULT] == '*default*') {
$options[GRADE_REPORT_PREFERENCE_DEFAULT] = get_string('reportdefault', 'grades', $default);
}
$label = get_string($lang_string, 'grades') . $number;
$mform->addElement($type, $full_pref, $label, $options);
if ($lang_string != 'showuserimage') {
$mform->addHelpButton($full_pref, $lang_string, 'grades');
}
$mform->setDefault($full_pref, $pref_value);
$mform->setType($full_pref, PARAM_ALPHANUM);
}
}
foreach($advanced as $name) {
$mform->setAdvanced('grade_report_'.$name);
}
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->setDefault('id', $course->id);
$this->add_action_buttons(false);
}
/// perform some extra moodle validation
function validation($data, $files) {
return parent::validation($data, $files);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Defines site config settings for the grader report
*
* @package gradereport_grader
* @copyright 2007 Moodle Pty Ltd (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
if ($ADMIN->fulltree) {
$strinherit = get_string('inherit', 'grades');
$strpercentage = get_string('percentage', 'grades');
$strreal = get_string('real', 'grades');
$strletter = get_string('letter', 'grades');
$settings->add(new admin_setting_configcheckbox('grade_report_showonlyactiveenrol', get_string('showonlyactiveenrol', 'grades'),
get_string('showonlyactiveenrol_help', 'grades'), 1));
$settings->add(new admin_setting_configcheckbox('grade_report_quickgrading', get_string('quickgrading', 'grades'),
get_string('quickgrading_help', 'grades'), 1));
$settings->add(new admin_setting_configselect('grade_report_meanselection', get_string('meanselection', 'grades'),
get_string('meanselection_help', 'grades'), GRADE_REPORT_MEAN_GRADED,
array(GRADE_REPORT_MEAN_ALL => get_string('meanall', 'grades'),
GRADE_REPORT_MEAN_GRADED => get_string('meangraded', 'grades'))));
$settings->add(new admin_setting_configcheckbox('grade_report_showaverages', get_string('showaverages', 'grades'),
get_string('showaverages_help', 'grades'), 1));
$settings->add(new admin_setting_configcheckbox('grade_report_showranges', get_string('showranges', 'grades'),
get_string('showranges_help', 'grades'), 0));
$settings->add(new admin_setting_configcheckbox('grade_report_showuserimage', get_string('showuserimage', 'grades'),
get_string('showuserimage_help', 'grades'), 1));
$settings->add(new admin_setting_configcheckbox('grade_report_shownumberofgrades', get_string('shownumberofgrades', 'grades'),
get_string('shownumberofgrades_help', 'grades'), 0));
$settings->add(new admin_setting_configselect('grade_report_averagesdisplaytype', get_string('averagesdisplaytype', 'grades'),
get_string('averagesdisplaytype_help', 'grades'), GRADE_REPORT_PREFERENCE_INHERIT,
array(GRADE_REPORT_PREFERENCE_INHERIT => $strinherit,
GRADE_DISPLAY_TYPE_REAL => $strreal,
GRADE_DISPLAY_TYPE_PERCENTAGE => $strpercentage,
GRADE_DISPLAY_TYPE_LETTER => $strletter)));
$settings->add(new admin_setting_configselect('grade_report_rangesdisplaytype', get_string('rangesdisplaytype', 'grades'),
get_string('rangesdisplaytype_help', 'grades'), GRADE_REPORT_PREFERENCE_INHERIT,
array(GRADE_REPORT_PREFERENCE_INHERIT => $strinherit,
GRADE_DISPLAY_TYPE_REAL => $strreal,
GRADE_DISPLAY_TYPE_PERCENTAGE => $strpercentage,
GRADE_DISPLAY_TYPE_LETTER => $strletter)));
$settings->add(new admin_setting_configselect('grade_report_averagesdecimalpoints', get_string('averagesdecimalpoints', 'grades'),
get_string('averagesdecimalpoints_help', 'grades'), GRADE_REPORT_PREFERENCE_INHERIT,
array(GRADE_REPORT_PREFERENCE_INHERIT => $strinherit,
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5')));
$settings->add(new admin_setting_configselect('grade_report_rangesdecimalpoints', get_string('rangesdecimalpoints', 'grades'),
get_string('rangesdecimalpoints_help', 'grades'), GRADE_REPORT_PREFERENCE_INHERIT,
array(GRADE_REPORT_PREFERENCE_INHERIT => $strinherit,
'0' => '0',
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5')));
}
+279
View File
@@ -0,0 +1,279 @@
/**
* Container.
*/
.path-grade-report-grader .gradeparent {
position: relative;
}
/**
* The table.
*/
.path-grade-report-grader .gradeparent table {
border: 1px solid #ccc;
border-collapse: separate;
border-spacing: 0;
border-bottom-width: 0;
border-right-width: 0;
margin-bottom: 2em;
height: 100%;
}
/**
* All the cells.
*/
.path-grade-report-grader .gradeparent .cell {
border: 1px solid #ccc;
border-top-width: 0;
border-left-width: 0;
padding: 0.5rem 1rem;
vertical-align: top;
height: 100%;
}
.path-grade-report-grader .overriddengrade {
color: #495057;
border: none;
-moz-appearance: none;
-webkit-appearance: none;
}
.path-grade-report-grader .gradepass {
color: #357a32;
}
.path-grade-report-grader .gradefail {
color: #ca3120;
}
.path-grade-report-grader .grade_icons,
.path-grade-report-grader .category_grade_icons {
white-space: nowrap;
margin-top: 0.5rem;
}
.path-grade-report-grader .grade_type_value .grade_icons {
margin-left: auto;
}
.path-grade-report-grader .grade_icons .icon {
margin-right: 1rem;
font-size: 14px;
width: 24px;
}
.path-grade-report-grader .grade_icons .icon:last-child {
margin-right: 0;
}
/**
* All the floating divs.
*/
.path-grade-report-grader .gradeparent .floater {
display: none;
}
.path-grade-report-grader .gradeparent .floating {
display: block;
}
/**
* All the headers + floating cells.
*/
.path-grade-report-grader .gradeparent .heading .cell,
.path-grade-report-grader .gradeparent .avg .cell,
.path-grade-report-grader .gradeparent .user.cell {
font-size: 14px;
font-weight: normal;
}
.path-grade-report-grader .gradeparent .heading .header,
.path-grade-report-grader .gradeparent .heading .userfield {
white-space: nowrap;
}
.path-grade-report-grader .heading .gradeitemheader {
text-indent: -2rem;
padding-left: 2rem;
padding-top: 6px;
}
.path-grade-report-grader .dropdown-item {
text-align: left;
}
.path-grade-report-grader .heading .gradeitemheader .itemicon {
text-align: right;
}
/**
* The user cells.
*/
.path-grade-report-grader .gradeparent .user.cell {
white-space: nowrap;
}
.path-grade-report-grader .gradeparent .highlightable.cell {
min-width: 200px;
}
.path-grade-report-grader .gradeparent .highlightable.cell.collapsed {
min-width: unset;
}
.path-grade-report-grader .gradeparent .user.cell .userpicture {
border: none;
vertical-align: middle;
}
.path-grade-report-grader .gradeitemheader {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
vertical-align: middle;
}
/**
* The footer's header.
*/
.path-grade-report-grader .gradeparent .range .header,
.path-grade-report-grader .gradeparent .avg .header,
.path-grade-report-grader .gradeparent .groupavg .header {
font-weight: bold;
text-align: right;
}
/**
* The footer's floating cells.
*/
.path-grade-report-grader .gradeparent .avg.floating .cell {
border-top-width: 1px;
}
/**
* Content styling.
*/
.path-grade-report-grader .gradeparent .heading .cell .iconsmall {
/* Fixes inconsistencies in cell height on IE. */
padding-top: 0;
padding-bottom: 0;
}
.path-grade-report-grader .gradeparent .sorticon {
vertical-align: middle;
margin: 0;
}
.path-grade-report-grader .gradeparent td.grade_type_value,
.path-grade-report-grader .gradeparent td.grade_type_value input,
.path-grade-report-grader .gradeparent td.grade_type_value div {
text-align: right;
margin-left: auto;
}
.path-grade-report-grader .gradeparent .gradevalue {
display: inline-block;
}
.path-grade-report-grader .header,
.path-grade-report-grader td.userfield div {
padding-top: 6px;
}
.path-grade-report-grader .gradeparent tr:nth-child(n) td.ajaxoverridden:nth-child(n) {
/* Made very specific to override the default stripped style of the table. */
background-color: #ffe3a0;
}
.path-grade-report-grader .gradeparent .excludedfloater {
font-weight: bold;
color: red;
font-size: 9px;
float: left;
}
.path-grade-report-grader .gradeparent .category {
text-align: left;
}
/**
* Editing fields.
* MDL-74286 - width 100% pushes action menu out of cell.
*/
.path-grade-report-grader .gradeparent select {
text-overflow: ellipsis;
min-width: 8rem;
}
.path-grade-report-grader .gradeparent .text {
border: 1px solid #666;
width: auto;
margin: 0;
padding: 0;
text-align: center;
}
.path-grade-report-grader .gradeparent table .action-menu {
display: inline-block;
margin-right: -0.5rem;
padding-left: 0.5rem;
}
.path-grade-report-grader .gradeparent table .heading .action-menu {
padding-left: 0;
}
.path-grade-report-grader .dropdown-menu {
width: max-content;
}
.path-grade-report-grader .initialsdropdown {
width: 460px;
}
.path-grade-report-grader .initialsdropdown::after {
content: '';
}
.path-grade-report-grader .usersearchwidget::after {
content: '';
}
.path-grade-report-grader .usersearchwidget {
pointer-events: none;
}
.path-grade-report-grader .usersearchwidget input {
pointer-events: initial;
z-index: 1;
}
.path-grade-report-grader .usersearchwidget button,
.path-grade-report-grader .usersearchwidget a {
pointer-events: initial;
}
.path-grade-report-grader .usersearchdropdown {
/*Extra height to allow non result items.*/
max-height: 400px;
width: 300px;
pointer-events: all;
padding: 0;
}
.path-grade-report-grader .usersearchdropdown .unsearchablecontentcontainer {
position: sticky;
bottom: 0;
background-color: white;
}
.path-grade-report-grader .usersearchdropdown .footer {
border-top: 1px solid #dee2e6;
font-size: 90%;
}
.collapsecolumndropdown.show {
width: 300px;
max-height: 300px;
}
@media only screen and (min-width: 768px) {
.path-grade-report-grader .gradeparent table {
padding-right: 6em;
}
}
@@ -0,0 +1,108 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/action_bar
Actions bar for the grader report.
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
Example context (json):
{
"generalnavselector": {
"name": "Gradebook tertiary navigation selector",
"value": "opt2",
"baseid": "select-menu56789",
"selectedoption": "Gradebook setup",
"options": [
{
"selected": false,
"isgroup": {
"name": "View",
"id": "select-menu-group1",
"options": [
{
"name": "Grader report",
"value": "opt1",
"id": "select-menu-option1",
"selected": false
}
]
}
},
{
"selected": false,
"isgroup": {
"name": "Setup",
"id": "select-menu-group2",
"options": [
{
"name": "Gradebook setup",
"value": "opt2",
"id": "select-menu-option2",
"selected": true
}
]
}
}
]
},
"groupselector": "<div class='group-selector'></div>",
"collapsedcolumns": "<div class='collapse-columns'></div>",
"pagereset": "http://moodle.local/grade/report/grader/index.php?id=2&userid=2&reset=1"
}
}}
<div class="container-fluid tertiary-navigation full-width-bottom-border">
<div class="row">
{{#generalnavselector}}
<div class="navitem">
{{>core/tertiary_navigation_selector}}
</div>
<div class="navitem-divider"></div>
{{/generalnavselector}}
{{#searchdropdown}}
<div class="navitem">
{{>core/comboboxsearch}}
</div>
<div class="navitem-divider"></div>
{{/searchdropdown}}
{{#groupselector}}
<div class="navitem">
{{{.}}}
</div>
<div class="navitem-divider"></div>
{{/groupselector}}
{{#initialselector}}
<div class="navitem d-flex flex-column align-self-center">
{{>core/comboboxsearch}}
</div>
<div class="navitem-divider"></div>
{{/initialselector}}
{{#pagereset}}
<div class="navitem align-self-center">
<a href="{{{.}}}" class="btn btn-link">{{#str}}clearall, gradereport_grader{{/str}}</a>
</div>
<div class="navitem-divider"></div>
{{/pagereset}}
{{#collapsedcolumns}}
<div class="navitem flex-column align-self-center ml-auto {{#classes}}{{.}}{{/classes}}" aria-live="polite">
{{#content}}
{{>core/comboboxsearch}}
{{/content}}
</div>
{{/collapsedcolumns}}
</div>
</div>
@@ -0,0 +1,42 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/headercell
Header cell template
Example context (json):
{
"headerlink": "<a class='gradeitemheader' href='index.php?id=2&amp;sortitemid=2'><img class='icon itemicon' src='http://moodlesite/theme/image.php/boost/assign/1680563323/monologo' alt='Assignment' />Assignment 1</a>",
"arrow": "<a title='Descending' aria-label='Descending' href='index.php?id=2&amp;sortitemid=2'><i class='icon fa fa-sort-desc fa-fw sorticon' aria-hidden='true' ></i></a>",
"singleview": "<div class='action-menu moodle-actionmenu grader' id='action-menu-8' data-enhance='moodle-core-actionmenu'></div>",
"statusicons": "<div class='grade_icons'><i class='icon fa fa-lock fa-fw text-muted' title='Locked' role='img' aria-label='Locked'></i></div>"
}
}}
<div class="d-flex flex-column h-100">
<div class="d-flex">
<div class="header">
{{{courseheader}}}
</div>
<div>
{{{actionmenu}}}
</div>
</div>
<div class="d-flex flex-grow-1 align-items-end">
{{{statusicons}}}
</div>
</div>
@@ -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/>.
}}
{{!
@template gradereport_grader/cell
Cell template.
Example context (json):
{
"iseditable": "true",
"statusicons": "<div class='grade_icons'><i class='icon fa fa-lock fa-fw text-muted' title='Locked' role='img' aria-label='Locked'></i></div>",
"actionmenu": "<div class='action-menu moodle-actionmenu grader' id='action-menu-8' data-enhance='moodle-core-actionmenu'></div>",
"id": "grade_313_624",
"label": "grade_313_624",
"title": "Grade",
"extraclasses": "statusicons",
"value": "Text information",
"tabindex": "1",
"name": "grade[313][624]"
}
}}
<div class="d-flex flex-column h-100" data-collapse="content">
<div class="d-flex">
<div class="d-flex flex-grow-1">
{{#iseditable}}
{{#scale}}
{{>core_grades/grades/grader/scale}}
{{/scale}}
{{^scale}}
{{>core_grades/grades/grader/input}}
{{/scale}}
{{/iseditable}}
{{^iseditable}}
{{>core_grades/grades/grader/text}}
{{/iseditable}}
</div>
<div>
{{{actionmenu}}}
</div>
</div>
<div class="d-flex flex-grow-1 align-items-end">
{{{statusicons}}}
</div>
</div>
@@ -0,0 +1,64 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/collapse/collapsebody
The body of the column collapsing dropdown that contains the form and subsequent results from the search.
Example context (json):
{
"instance": 25,
"results": [
{
"name": "42",
"displayName": "The meaning of life",
"category": "Hitchhikers grade category"
}
],
"searchTerm": "Meaning of",
"userid": "42"
}
}}
<div class="flex-column h-100 w-100">
<span class="d-none" data-region="userid" data-userid="{{userid}}" aria-hidden="true"></span>
{{< core/search_input_auto }}
{{$label}}{{#str}}
searchcollapsedcolumns, core_grades
{{/str}}{{/label}}
{{$placeholder}}{{#str}}
searchcollapsedcolumns, core_grades
{{/str}}{{/placeholder}}
{{/ core/search_input_auto }}
<form class="columnsdropdownform flex-column h-100">
<fieldset>
<legend class="sr-only">{{#str}} aria:dropdowncolumns, gradereport_grader {{/str}}</legend>
<ul id="collapse-{{instance}}-listbox" class="searchresultitemscontainer overflow-auto py-2 px-1 list-group mx-0 text-truncate" data-region="search-result-items-container">
{{>gradereport_grader/collapse/collapseresults}}
</ul>
</fieldset>
<div class="d-flex mt-2">
<div class="d-flex align-items-center form-check">
<label id="check-all" class="selected-option-info text-truncate form-check-label d-block">
<input disabled class="form-check-input" type="checkbox" data-action="selectall">
{{#str}}selectall, core{{/str}}
</label>
</div>
<div class="flex-row ml-auto">
<input class="btn btn-outline-secondary pull-right mx-2" data-action="cancel" type="submit" value="{{#str}}closebuttontitle{{/str}}">
<input disabled class="btn btn-primary pull-right" data-action="save" type="submit" value="{{#str}}expand{{/str}}">
</div>
</div>
</form>
</div>
@@ -0,0 +1,41 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/collapse/collapseresultitems
Context variables required for this template:
* name - The 'system' name that we search against.
* displayName - The 'end user' name that the user can search against.
* category - The category that a gradable item is within.
Example context (json):
{
"name": "42",
"displayName": "The meaning of life",
"category": "Hitchhikers grade category"
}
}}
<li class="w-100 result-row form-check mb-1">
<label class="selected-option-info d-block pr-3 text-truncate form-check-label">
<input class="form-check-input" data-collapse="{{name}}" type="checkbox" value="">
<span class="selected-option-text w-100 p-0">
{{{displayName}}}
</span>
{{#category}}
<span class="d-block w-100 pull-left text-muted text-truncate small">
{{category}}
</span>
{{/category}}
</label>
</li>
@@ -0,0 +1,38 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/collapse/collapseresults
Context variables required for this template:
* results - An array of found columns that are currently hidden.
* searchTerm - What the user is currently searching for.
Example context (json):
{
"results": [
{
"name": "42",
"displayName": "The meaning of life",
"category": "Hitchhikers grade category"
}
],
"searchTerm": "Meaning of"
}
}}
{{#results}}
{{>gradereport_grader/collapse/collapseresultitems}}
{{/results}}
{{^results}}
<span class="d-block my-4">{{#str}} noresultsfor, core, {{searchTerm}}{{/str}}</span>
{{/results}}
@@ -0,0 +1,29 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/collapse/icon
Context variables required for this template:
* field - Either the shortname of the user field or a grade item ID.
Example context (json):
{
"field": "42",
"name": "Meaning of life"
}
}}
<button type="button" class="btn btn-link btn-icon icon-size-3" data-hider="expand" data-col="{{field}}">
<i class="icon fa fa-plus m-0" title="{{#str}}reopencolumn, gradereport_grader, {{name}}{{/str}}" aria-hidden="true"></i>
<span class="sr-only">{{#str}}reopencolumn, gradereport_grader, {{name}}{{/str}}</span>
</button>
@@ -0,0 +1,48 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/headercell
Header cell template
Example context (json):
{
"headerlink": "<a class='gradeitemheader' href='index.php?id=2&amp;sortitemid=2'><img class='icon itemicon' src='http://moodlesite/theme/image.php/boost/assign/1680563323/monologo' alt='Assignment' />Assignment 1</a>",
"arrow": "<a title='Descending' aria-label='Descending' href='index.php?id=2&amp;sortitemid=2'><i class='icon fa fa-sort-desc fa-fw sorticon' aria-hidden='true' ></i></a>",
"singleview": "<div class='action-menu moodle-actionmenu grader' id='action-menu-8' data-enhance='moodle-core-actionmenu'></div>",
"statusicons": "<div class='grade_icons'><i class='icon fa fa-lock fa-fw text-muted' title='Locked' role='img' aria-label='Locked'></i></div>"
}
}}
<div class="d-flex flex-column h-100">
<div class="d-none flex-grow-1 align-items-start" data-collapse="expandbutton">
{{{collapsedicon}}}
</div>
<div class="d-flex" data-collapse="content">
<div class="d-flex flex-grow-1">
{{{headerlink}}}
<div class="mx-2 mt-2">
{{{arrow}}}
</div>
</div>
<div>
{{{singleview}}}
</div>
</div>
<div class="d-flex flex-grow-1 align-items-end">
{{{statusicons}}}
</div>
</div>
@@ -0,0 +1,51 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_grader/perpage
The students per page dropdown element.
Context variables required for this template:
* baseurl - The base URL for the perpage action.
* options - Choices
Example context (json):
{
"baseurl": "http://example.com/grade/report/grader/index.php?id=2&report=grader",
"options": [
{"name": "20", "value": "20"},
{"name": "100", "value": "100", "selected": true},
{"name": "All", "value": "0"}
]
}
}}
<label>
{{#str}}show{{/str}}
<select name="perpage" class="custom-select ignoredirty" id="{{uniqid}}">
{{#options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{{name}}}</option>
{{/options}}
</select>
</label>
{{#js}}
document.getElementById('{{uniqid}}').addEventListener('change', function(e) {
var url = new URL('{{baseurl}}');
url.searchParams.set('perpage', e.target.value);
window.location.href = url;
});
{{/js}}
@@ -0,0 +1,147 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Behat steps definitions for drag and drop onto image.
*
* @package gradereport_grader
* @category test
* @copyright 2015 Oakland University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
use Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
* Steps definitions related with the drag and drop onto image question type.
*
* @copyright 2015 Oakland University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_gradereport_grader extends behat_base {
/**
* Gets the user id from its name.
*
* @throws Exception
* @param string $name
* @return int
*/
protected function get_user_id($name) {
global $DB;
$names = explode(' ', $name);
if (!$id = $DB->get_field('user', 'id', array('firstname' => $names[0], 'lastname' => $names[1]))) {
throw new Exception('The specified user with username "' . $name . '" does not exist');
}
return $id;
}
/**
* Gets the grade item id from its name.
*
* @deprecated since 4.2
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @throws Exception
* @param string $itemname
* @return int
*/
protected function get_grade_item_id($itemname) {
global $DB;
debugging('behat_gradereport_grader::get_grade_item_id() is deprecated, please use' .
' behat_grades::get_grade_item_id() instead.', DEBUG_DEVELOPER);
if ($id = $DB->get_field('grade_items', 'id', array('itemname' => $itemname))) {
return $id;
}
// The course total is a special case.
if ($itemname === "Course total") {
if (!$id = $DB->get_field('grade_items', 'id', array('itemtype' => 'course'))) {
throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
}
return $id;
}
// Find a category with the name.
if ($catid = $DB->get_field('grade_categories', 'id', array('fullname' => $itemname))) {
if ($id = $DB->get_field('grade_items', 'id', array('iteminstance' => $catid))) {
return $id;
}
}
throw new Exception('The specified grade_item with name "' . $itemname . '" does not exist');
}
/**
* Clicks on given user menu.
*
* @Given /^I click on user menu "([^"]*)"$/
* @param string $student
*/
public function i_click_on_user_menu(string $student) {
$xpath = $this->get_user_selector($student);
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
}
/**
* Gets unique xpath selector for a user.
*
* @throws Exception
* @param string $student
* @return string
*/
protected function get_user_selector(string $student): string {
$userid = $this->get_user_id($student);
return "//table[@id='user-grades']//*[@data-type='user'][@data-id='" . $userid . "']";
}
/**
* Clicks on given user profile field menu.
*
* @Given /^I click on user profile field menu "([^"]*)"$/
* @param string $field
*/
public function i_click_on_user_profile_field_menu(string $field) {
$xpath = "//table[@id='user-grades']//*[@data-type='" . mb_strtolower($field) . "']";
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
}
/**
* Return the list of partial named selectors.
*
* @return array
*/
public static function get_partial_named_selectors(): array {
return [
new behat_component_named_selector(
'collapse search',
[".//*[contains(concat(' ', @class, ' '), ' collapsecolumndropdown ')]"]
),
];
}
}
@@ -0,0 +1,250 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
use Behat\Gherkin\Node\TableNode;
require_once(__DIR__ . '/../../../../../lib/behat/behat_deprecated_base.php');
/**
* Steps definitions that are now deprecated and will be removed in the next releases.
*
* This file only contains the steps that previously were in the behat_*.php files in the SAME DIRECTORY.
* When deprecating steps from other components or plugins, create a behat_COMPONENT_deprecated.php
* file in the same directory where the steps were defined.
*
* @package gradereport_grader
* @category test
* @copyright 2023 Ilya Tregubov
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_gradereport_grader_deprecated extends behat_deprecated_base {
/**
* Remove focus for a grade value cell.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" feedback$/
* @param string $student
* @param string $itemname
*/
public function i_click_away_from_student_and_grade_feedback($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_click_away_from_student_and_grade_feedback']);
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
$this->execute('behat_general::i_take_focus_off_field', array($this->escape($xpath), 'xpath_element'));
}
/**
* Look for a feedback editing field.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Then /^I should see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
* @param string $student
* @param string $itemname
*/
public function i_should_see_feedback_field($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_should_see_feedback_field']);
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
$this->execute('behat_general::should_be_visible', array($this->escape($xpath), 'xpath_element'));
}
/**
* Look for a lack of the feedback editing field.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Then /^I should not see a feedback field for "([^"]*)" and grade item "([^"]*)"$/
* @param string $student
* @param string $itemname
*/
public function i_should_not_see_feedback_field($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_should_not_see_feedback_field']);
$xpath = $this->get_student_and_grade_feedback_selector($student, $itemname);
$this->execute('behat_general::should_not_exist', array($this->escape($xpath), 'xpath_element'));
}
/**
* Gets xpath for a particular student/grade item feedback cell.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @throws Exception
* @param string $student
* @param string $itemname
* @return string
*/
protected function get_student_and_grade_feedback_selector($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::get_student_and_grade_feedback_selector']);
$cell = $this->get_student_and_grade_cell_selector($student, $itemname);
return $cell . "//input[contains(@id, 'feedback_') or @name='ajaxfeedback']";
}
/**
* Click a given user grade cell.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Given /^I click on student "([^"]*)" for grade item "([^"]*)"$/
* @param string $student
* @param string $itemname
*/
public function i_click_on_student_and_grade_item($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_click_on_student_and_grade_item']);
$xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
}
/**
* Remove focus for a grade value cell.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Given /^I click away from student "([^"]*)" and grade item "([^"]*)" value$/
* @param string $student
* @param string $itemname
*/
public function i_click_away_from_student_and_grade_value($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_click_away_from_student_and_grade_value']);
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
$this->execute('behat_general::i_take_focus_off_field', array($this->escape($xpath), 'xpath_element'));
}
/**
* Checks grade values with or without a edit box.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Then /^the grade for "([^"]*)" in grade item "([^"]*)" should match "([^"]*)"$/
* @throws Exception
* @throws ElementNotFoundException
* @param string $student
* @param string $itemname
* @param string $value
*/
public function the_grade_should_match($student, $itemname, $value) {
$this->deprecated_message(['behat_gradereport_grader::the_grade_should_match']);
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
$gradefield = $this->getSession()->getPage()->find('xpath', $xpath);
if (!empty($gradefield)) {
// Get the field.
$fieldtype = behat_field_manager::guess_field_type($gradefield, $this->getSession());
if (!$fieldtype) {
throw new Exception('Could not get field type for grade field "' . $itemname . '"');
}
$field = behat_field_manager::get_field_instance($fieldtype, $gradefield, $this->getSession());
if (!$field->matches($value)) {
$fieldvalue = $field->get_value();
throw new ExpectationException(
'The "' . $student . '" and "' . $itemname . '" grade is "' . $fieldvalue . '", "' . $value . '" expected' ,
$this->getSession()
);
}
} else {
// If there isn't a form field, just search for contents.
$valueliteral = behat_context_helper::escape($value);
$xpath = $this->get_student_and_grade_cell_selector($student, $itemname);
$xpath .= "[contains(normalize-space(.)," . $valueliteral . ")]";
$node = $this->getSession()->getDriver()->find($xpath);
if (empty($node)) {
$locatorexceptionmsg = 'Cell for "' . $student . '" and "' . $itemname . '" with value "' . $value . '"';
throw new ElementNotFoundException($this->getSession(), $locatorexceptionmsg, null, $xpath);
}
}
}
/**
* Look for a grade editing field.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Then /^I should see a grade field for "([^"]*)" and grade item "([^"]*)"$/
* @param string $student
* @param string $itemname
*/
public function i_should_see_grade_field($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_should_see_grade_field']);
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
$this->execute('behat_general::should_be_visible', array($this->escape($xpath), 'xpath_element'));
}
/**
* Look for a lack of the grade editing field.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @Then /^I should not see a grade field for "([^"]*)" and grade item "([^"]*)"$/
* @param string $student
* @param string $itemname
*/
public function i_should_not_see_grade_field($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::i_should_not_see_grade_field']);
$xpath = $this->get_student_and_grade_value_selector($student, $itemname);
$this->execute('behat_general::should_not_exist', array($this->escape($xpath), 'xpath_element'));
}
/**
* Gets unique xpath selector for a student/grade item combo.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @throws Exception
* @param string $student
* @param string $itemname
* @return string
*/
protected function get_student_and_grade_cell_selector($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::get_student_and_grade_cell_selector']);
$itemid = 'u' . $this->get_user_id($student) . 'i' . $this->get_grade_item_id($itemname);
return "//table[@id='user-grades']//td[@id='" . $itemid . "']";
}
/**
* Gets xpath for a particular student/grade item grade value cell.
*
* @deprecated since 4.2 - we don't allow ajax edit on grader report anymore.
* @todo MDL-77107 This will be deleted in Moodle 4.6.
* @throws Exception
* @param string $student
* @param string $itemname
* @return string
*/
protected function get_student_and_grade_value_selector($student, $itemname) {
$this->deprecated_message(['behat_gradereport_grader::get_student_and_grade_value_selector']);
$cell = $this->get_student_and_grade_cell_selector($student, $itemname);
return $cell . "//*[contains(@id, 'grade_') or @name='ajaxgrade']";
}
}
@@ -0,0 +1,288 @@
@core @javascript @gradereport @gradereport_grader
Feature: Within the grader report, test that we can collapse columns
In order to reduce usage of visual real estate
As a teacher
I need to be able to change how the report is displayed
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "grade categories" exist:
| fullname | course |
| Some cool grade category | C1 |
And the following "custom profile fields" exist:
| datatype | shortname | name |
| text | enduro | Favourite enduro race |
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 "activities" exist:
| activity | course | idnumber | name | gradecategory |
| assign | C1 | a1 | Test assignment one | Some cool grade category |
| assign | C1 | a3 | Test assignment three | Some cool grade category |
And the following "activities" exist:
| activity | course | idnumber | name |
| assign | C1 | a2 | Test assignment two |
| assign | C1 | a4 | Test assignment four |
And the following config values are set as admin:
| showuseridentity | idnumber,email,city,country,phone1,phone2,department,institution,profile_field_enduro |
And I change window size to "large"
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
Scenario: An admin collapses a user info column and then reloads the page to find the column still collapsed
Given "Email" "text" in the "First name / Last name" "table_row" should be visible
And I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
And I click on user profile field menu "profile_field_enduro"
And I choose "Collapse" in the open action menu
And "Favourite enduro race" "text" in the "First name / Last name" "table_row" should not be visible
When I reload the page
Then "Email" "text" in the "First name / Last name" "table_row" should not be visible
# Check that the collapsed column is only for the user that set it.
And I am on the "Course 1" "Course" page logged in as "admin"
And I change window size to "large"
And I navigate to "View > Grader report" in the course gradebook
And "Email" "text" in the "First name / Last name" "table_row" should be visible
Scenario: A teacher collapses a grade item column and then reloads the page to find the column still collapsed
Given "Test assignment one" "link" in the "First name / Last name" "table_row" should be visible
And I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And "Test assignment one" "link" in the "First name / Last name" "table_row" should not be visible
When I reload the page
And "Test assignment one" "link" in the "First name / Last name" "table_row" should not be visible
Scenario: When a user collapses a column, inform them within the report and tertiary nav area
Given I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page
When I choose "Collapse" in the open action menu
And "Test assignment one" "link" in the "First name / Last name" "table_row" should not be visible
Then I should see "Expand column Test assignment one"
And I should see "Collapsed columns 1"
Scenario: Collapsed columns can have their name searched and triggered to expand but the contents are not searched
Given "ID number" "text" in the "First name / Last name" "table_row" should be visible
And I click on user profile field menu "idnumber"
And I choose "Collapse" in the open action menu
# Opens the tertiary trigger button.
And I click on "Collapsed columns" "combobox"
# This is checking that the column name search dropdown exists.
And I wait until "Search collapsed columns" "field" exists
# Default state contains the collapsed column names.
And I should see "ID number"
# Search for a column that was not hidden.
When I set the field "Search collapsed columns" to "Email"
And I should see "No results for \"Email\""
# Search for a ID number value inside the column that was hidden.
Then I set the field "Search collapsed columns" to "s5"
And I should see "No results for \"s5\""
# Search for a column that was hidden.
And I set the field "Search collapsed columns" to "ID"
And I should see "ID number"
Scenario: Expand multiple columns at once
Given I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on grade item menu "Test assignment two" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on grade item menu "Test assignment three" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on grade item menu "Test assignment four" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone1"
And I choose "Collapse" in the open action menu
And I click on "Collapsed columns" "combobox"
# This is checking that the column name search dropdown exists.
When I wait until "Search collapsed columns" "field" exists
And I click on "Test assignment one" "checkbox" in the "form" "gradereport_grader > collapse search"
And I click on "Test assignment three" "checkbox" in the "form" "gradereport_grader > collapse search"
And I click on "Phone" "checkbox" in the "form" "gradereport_grader > collapse search"
And I click on "Expand" "button" in the "form" "gradereport_grader > collapse search"
And "Test assignment one" "link" in the "First name / Last name" "table_row" should be visible
And "Test assignment three" "link" in the "First name / Last name" "table_row" should be visible
And "Phone" "text" in the "First name / Last name" "table_row" should be visible
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
And "Test assignment two" "link" in the "First name / Last name" "table_row" should not be visible
And "Test assignment four" "link" in the "First name / Last name" "table_row" should not be visible
Scenario: If there is only one collapsed column it expands
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
When I press "Expand column Email"
And I wait until the page is ready
Then "Email" "text" in the "First name / Last name" "table_row" should be visible
Scenario: When a grade item is collapsed, the grade category is shown alongside the column name.
Given I click on grade item menu "Test assignment one" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on grade item menu "Test assignment two" of type "gradeitem" on "grader" page
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And "Test assignment one" "link" in the "First name / Last name" "table_row" should not be visible
And "Test assignment two" "link" in the "First name / Last name" "table_row" should not be visible
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
# Opens the tertiary trigger button.
When I click on "Collapsed columns" "combobox"
# This is checking that the column name search dropdown exists.
And I wait until "Search collapsed columns" "field" exists
# Add ordering test as well.
And I should see "Test assignment one" in the "form" "gradereport_grader > collapse search"
And I should see "Some cool grade category" in the "form" "gradereport_grader > collapse search"
And I should see "Test assignment two" in the "form" "gradereport_grader > collapse search"
And I should see "Course 1" in the "form" "gradereport_grader > collapse search"
And I should see "Email" in the "form" "gradereport_grader > collapse search"
And I should not see "Category div" in the "form" "gradereport_grader > collapse search"
Scenario: Toggling edit mode should not show all collapsed columns
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
When I turn editing mode on
And I wait until the page is ready
Then "Email" "text" in the "First name / Last name" "table_row" should not be visible
Scenario: Resulting columns from hidden grade categories cant be collapsed
# Hiding columns already tested elsewhere, これはこれ、それはそれ。
Given I click on grade item menu "Some cool grade category" of type "category" on "grader" page
And I choose "Show totals only" in the open action menu
And I should not see "Test assignment name 1"
And I should see "Some cool grade category total"
When I click on grade item menu "Some cool grade category" of type "category" on "grader" page
Then I should not see "Collapse" in the ".dropdown-menu.show" "css_element"
@accessibility
Scenario: A teacher can manipulate the report display in an accessible way
# Hide a bunch of columns.
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone1"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone2"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Country"
And I choose "Collapse" in the open action menu
# Basic tests for the page.
When I click on "Collapsed columns" "combobox"
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
Then the focused element is "Search collapsed columns" "field"
And I press the escape key
And the focused element is "Collapsed columns" "combobox"
And I click on "Collapsed columns" "combobox"
# Lets check the tabbing order.
And I set the field "Search collapsed columns" to "phone"
And I wait until "Mobile phone" "checkbox" exists
And I press the tab key
And the focused element is "Clear search input" "button" in the ".dropdown-menu.show" "css_element"
And I press the escape key
And I press the tab key
# The course grade category menu.
And the focused element is "Cell actions" "button"
# Tab over to the collapsed columns.
And I click on user profile field menu "city"
And I press the escape key
And I press the tab key
And the focused element is "Expand column Country" "button"
And I press the enter key
And I press the tab key
And the focused element is "Expand column Phone" "button"
And I press the enter key
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
And "Phone" "text" in the "First name / Last name" "table_row" should be visible
And "Mobile phone" "text" in the "First name / Last name" "table_row" should not be visible
And "Country" "text" in the "First name / Last name" "table_row" should be visible
# Ensure that things did not start failing after we did some manipulation.
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
Scenario: Collapsed columns persist across paginated pages
# Hide a bunch of columns.
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone1"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone2"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Country"
And I choose "Collapse" in the open action menu
# Ensure we are ready to move onto the next step.
When I should see "Collapsed columns 4"
# Confirm our columns are hidden.
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
And "Phone" "text" in the "First name / Last name" "table_row" should not be visible
And "Mobile phone" "text" in the "First name / Last name" "table_row" should not be visible
And "Country" "text" in the "First name / Last name" "table_row" should not be visible
# Navigate to the next paginated page and ensure our columns are still hidden.
Then I set the field "perpage" to "100"
And I should see "Collapsed columns 4"
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
And "Phone" "text" in the "First name / Last name" "table_row" should not be visible
And "Mobile phone" "text" in the "First name / Last name" "table_row" should not be visible
And "Country" "text" in the "First name / Last name" "table_row" should not be visible
Scenario: If a column is actively sorted and then collapsed the active sort on the page should become First name
# This behaviour is inline with other tables where we collapse columns that are sortable.
Given I click on user profile field menu "Email"
And I choose "Descending" in the open action menu
And I wait to be redirected
And I click on user profile field menu "Email"
When I choose "Collapse" in the open action menu
And I wait to be redirected
And "Email" "text" in the "First name / Last name" "table_row" should not be visible
Then "Dummy User" "table_row" should appear before "Student 1" "table_row"
And "Student 1" "table_row" should appear before "Turtle Manatee" "table_row"
And "Turtle Manatee" "table_row" should appear before "User Example" "table_row"
Scenario: If multiple columns are collapsed, then all the user to expand all of them at once
# Hide a bunch of columns.
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone1"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Phone2"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Country"
And I choose "Collapse" in the open action menu
# Ensure we are ready to move onto the next step.
And I wait until "Collapsed columns" "combobox" exists
When I click on "Collapsed columns" "combobox"
And I click on "Select all" "checkbox"
And I click on "Expand" "button" in the "form" "gradereport_grader > collapse search"
# All of the previously collapsed columns should now be visible.
And "Email" "text" in the "First name / Last name" "table_row" should be visible
And "Phone" "text" in the "First name / Last name" "table_row" should be visible
And "Mobile phone" "text" in the "First name / Last name" "table_row" should be visible
And "Country" "text" in the "First name / Last name" "table_row" should be visible
Scenario: If multiple columns are collapsed, when selecting all and then unselecting an option, the select all is then unchecked
# Hide some columns.
Given I click on user profile field menu "Email"
And I choose "Collapse" in the open action menu
And I click on user profile field menu "Country"
And I choose "Collapse" in the open action menu
# Ensure we are ready to move onto the next step.
And I wait until "Collapsed columns" "combobox" exists
When I click on "Collapsed columns" "combobox"
And I click on "Select all" "checkbox"
And I click on "Email" "checkbox" in the "form" "gradereport_grader > collapse search"
# The select all option should now be unchecked, Checking the form or option role is iffy with behat so we use the id.
Then the field "Select all" matches value ""
@@ -0,0 +1,111 @@
@core @core_grades @gradereport_grader @javascript
Feature: Group searching functionality within the grader report.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
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 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "groups" exist:
| name | course | idnumber |
| Default group | C1 | dg |
| Group 2 | C1 | g2 |
| Tutor group | C1 | tg |
| Marker group | C1 | mg |
And the following "group members" exist:
| user | group |
| student1 | dg |
| student2 | g2 |
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
Scenario: A teacher can see the 'group' search widget only when group mode is enabled in the course
Given ".groupsearchwidget" "css_element" should exist
And I am on the "C1" "course editing" page
And I set the following fields to these values:
| id_groupmode | No groups |
And I press "Save and display"
When I navigate to "View > Grader report" in the course gradebook
Then ".groupsearchwidget" "css_element" should not exist
Scenario: A teacher can search for and find a group to display
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 until "Marker group" "option_role" does not exist
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
# 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 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
Given I click on ".groupsearchwidget" "css_element"
# 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.
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 "Search groups" "field"
And ".active" "css_element" should exist in the "All participants" "option_role"
And I press the up key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "Tutor group" "option_role"
And I press the down key
And the focused element is "Search groups" "field"
And ".active" "css_element" should exist in the "All participants" "option_role"
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 "Search groups" "field"
And ".active" "css_element" should exist in the "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
@@ -0,0 +1,63 @@
Feature: Locking Grade Items and Categories in Gradebook
In order to ensure that grade items and categories can be securely locked in the gradebook,
As a teacher,
I need to perform locking actions and verify the locking status.
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "users" exist:
| username | firstname | lastname |
| teacher1 | Teacher | 1 |
| student1 | Student | 1 |
| student2 | Student | 2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Category 1 | C1 |
And the following "grade items" exist:
| itemname | course | gradecategory |
| Manual grade 1 | C1 | Category 1 |
| Manual grade 2 | C1 | Category 1 |
And the following "grade items" exist:
| itemname | course |
| Manual grade 3 | C1 |
@javascript
Scenario: Locking and unlocking a grade item preserves individual student locks
Given I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I change window size to "large"
When I click on "Manual grade 1" "core_grades > grade_actions" in the "Student 1" "table_row"
And I choose "Lock" in the open action menu
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
And I choose "Lock" in the open action menu
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
And I choose "Unlock" in the open action menu
Then "Locked" "icon" should exist in the "Student 1" "table_row"
And "Locked" "icon" should not exist in the "Student 2" "table_row"
@javascript
Scenario: Locking and unlocking a grade item through editing form preserves individual student locks
Given I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I change window size to "large"
When I click on "Manual grade 1" "core_grades > grade_actions" in the "Student 1" "table_row"
And I choose "Edit grade" in the open action menu
And I set the field "Locked" to "1"
And I press "Save changes"
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
And I choose "Edit grade item" in the open action menu
And I set the field "Locked" to "1"
And I click on "Save" "button" in the "Edit grade item" "dialogue"
And I click on grade item menu "Manual grade 1" of type "gradeitem" on "grader" page
And I choose "Edit grade item" in the open action menu
And I set the field "Locked" to "0"
And I click on "Save" "button" in the "Edit grade item" "dialogue"
Then "Locked" "icon" should exist in the "Student 1" "table_row"
And "Locked" "icon" should not exist in the "Student 2" "table_row"
@@ -0,0 +1,87 @@
@gradereport @gradereport_grader
Feature: grader report pagination
In order to consume the content of the report better
As a teacher
I need the report to be paginated
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
@javascript
Scenario: Default is used when teachers have no preference yet
Given "41" "users" exist with the following data:
| username | student[count] |
| firstname | Student |
| lastname | [count] |
| email | student[count]@example.com |
And "41" "course enrolments" exist with the following data:
| user | student[count] |
| course | C1 |
| role |student |
When I am on the "Course 1" "Course" page logged in as "teacher1"
And I navigate to "View > Grader report" in the course gradebook
Then the field "perpage" matches value "20"
# Add 3 to the expected number because there are 2 header and 1 footer rows.
And I should see "23" node occurrences of type "tr" in the "user-grades" "table"
And I should see "3" in the ".stickyfooter .pagination" "css_element"
And I should not see "4" in the ".stickyfooter .pagination" "css_element"
@javascript
Scenario: Teachers can have their preference for the number of students
Given the following "courses" exist:
| fullname | shortname |
| Course 2 | C2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C2 | editingteacher |
When I am on the "Course 1" "Course" page logged in as "teacher1"
And I navigate to "View > Grader report" in the course gradebook
And I set the field "perpage" to "100"
And I am on the "Course 2" "Course" page
And I navigate to "View > Grader report" in the course gradebook
Then the field "perpage" matches value "100"
@javascript
Scenario: Teachers can change the number of students shown on the report
Given "101" "users" exist with the following data:
| username | student[count] |
| firstname | Student |
| lastname | [count] |
| email | student[count]@example.com |
And "101" "course enrolments" exist with the following data:
| user | student[count] |
| course | C1 |
| role |student |
When I am on the "Course 1" "Course" page logged in as "teacher1"
And I navigate to "View > Grader report" in the course gradebook
And I set the field "perpage" to "100"
# Add 3 to the expected number because there are 2 header and 1 footer rows.
Then I should see "103" node occurrences of type "tr" in the "user-grades" "table"
And I should see "2" in the ".stickyfooter .pagination" "css_element"
And I should not see "3" in the ".stickyfooter .pagination" "css_element"
@javascript
Scenario: The pagination bar is only displayed when there is more than one page
Given "21" "users" exist with the following data:
| username | student[count] |
| firstname | Student |
| lastname | [count] |
| email | student[count]@example.com |
And "21" "course enrolments" exist with the following data:
| user | student[count] |
| course | C1 |
| role |student |
When I am on the "Course 1" "Course" page logged in as "teacher1"
And I navigate to "View > Grader report" in the course gradebook
# By default, we have 20 students per page.
Then ".stickyfooter .pagination" "css_element" should exist
And I set the field "perpage" to "100"
Then ".stickyfooter .pagination" "css_element" should not exist
@@ -0,0 +1,79 @@
@gradereport @gradereport_grader
Feature: We can sort grades/user fields on the grader report
In order to manage grades on grader report
As a teacher
I need to be able to sort grades or user fields.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | StudentA | 2 | d@example.com |
| student2 | StudentB | 4 | a@example.com |
| student3 | StudentC | 3 | c@example.com |
| student4 | StudentD | 1 | b@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
And the following "activities" exist:
| activity | course | section | name | intro | assignsubmission_onlinetext_enabled | submissiondrafts |
| assign | C1 | 1 | Test assignment name 1 | Submit your online text | 1 | 0 |
And the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name 1 | student1 | This is a submission for assignment 1 |
| Test assignment name 1 | student2 | This is a submission for assignment 1 |
| Test assignment name 1 | student3 | This is a submission for assignment 1 |
| Test assignment name 1 | student4 | This is a submission for assignment 1 |
And the following "grade items" exist:
| itemname | grademin | grademax | course |
| Manual grade | 20 | 40 | C1 |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name 1 | student1 | 80 |
| Test assignment name 1 | student2 | 40 |
| Test assignment name 1 | student3 | 60 |
And I log in as "teacher1"
@javascript
Scenario: Sort grades or user fields on grader report by using cell action menu
When I am on "Course 1" course homepage with editing mode on
And I navigate to "View > Grader report" in the course gradebook
# Default sorting is lastname ascending.
And "StudentD 1" "table_row" should appear before "StudentA 2" "table_row"
And "StudentA 2" "table_row" should appear before "StudentC 3" "table_row"
And "StudentC 3" "table_row" should appear before "StudentB 4" "table_row"
# Sort by grades in descending order.
And I click on grade item menu "Test assignment name 1" of type "gradeitem" on "grader" page
And I choose "Descending" in the open action menu
And I wait until the page is ready
Then "StudentA 2" "table_row" should appear before "StudentC 3" "table_row"
And "StudentC 3" "table_row" should appear before "StudentB 4" "table_row"
And "StudentB 4" "table_row" should appear before "StudentD 1" "table_row"
# Sort by grades in ascending order.
And I click on grade item menu "Test assignment name 1" of type "gradeitem" on "grader" page
And I choose "Ascending" in the open action menu
And I wait until the page is ready
Then "StudentD 1" "table_row" should appear before "StudentB 4" "table_row"
And "StudentB 4" "table_row" should appear before "StudentC 3" "table_row"
And "StudentC 3" "table_row" should appear before "StudentA 2" "table_row"
# Sort by email in ascending order.
And I click on user profile field menu "email"
And I choose "Ascending" in the open action menu
And I wait until the page is ready
Then "StudentB 4" "table_row" should appear before "StudentD 1" "table_row"
And "StudentD 1" "table_row" should appear before "StudentC 3" "table_row"
And "StudentC 3" "table_row" should appear before "StudentA 2" "table_row"
And I click on user profile field menu "email"
# Sort by email in descending order.
And I choose "Descending" in the open action menu
And I wait until the page is ready
Then "StudentA 2" "table_row" should appear before "StudentC 3" "table_row"
And "StudentC 3" "table_row" should appear before "StudentD 1" "table_row"
And "StudentD 1" "table_row" should appear before "StudentB 4" "table_row"
@@ -0,0 +1,105 @@
@gradereport @gradereport_grader
Feature: We can change what we are viewing on the grader report
In order to check the expected results are displayed
As a teacher
I need to assign grades and check that they display correctly in the gradebook when switching between views.
Background:
Given the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
| student2 | Student | 2 | student2@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "activities" exist:
| activity | course | section | name | intro | assignsubmission_onlinetext_enabled | submissiondrafts |
| assign | C1 | 1 | Test assignment name 1 | Submit your online text | 1 | 0 |
| assign | C1 | 1 | Test assignment name 2 | submit your online text | 1 | 0 |
And the following "mod_assign > submissions" exist:
| assign | user | onlinetext |
| Test assignment name 1 | student1 | This is a submission for assignment 1 |
| Test assignment name 2 | student1 | This is a submission for assignment 2 |
And the following "grade items" exist:
| itemname | grademin | grademax | course |
| Manual grade | 20 | 40 | C1 |
And the following "grade grades" exist:
| gradeitem | user | grade |
| Test assignment name 1 | student1 | 80 |
| Test assignment name 2 | student1 | 90 |
| Manual grade | student1 | 30 |
And I log in as "teacher1"
@javascript
Scenario: View and minimise the grader report containing hidden activities
When I am on "Course 1" course homepage with editing mode on
And I open "Test assignment name 2" actions menu
And I choose "Hide" in the open action menu
And I am on "Course 1" course homepage with editing mode off
And I navigate to "View > Grader report" in the course gradebook
And I should see "Test assignment name 1" in the "user-grades" "table"
And I should see "Test assignment name 2" in the "user-grades" "table"
And I should see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- | -6- |
| Student 1 | student1@example.com | 80 | 90 | 30 | 170 |
And I click on grade item menu "Course 1" of type "course" on "grader" page
And I choose "Show totals only" in the open action menu
And I should not see "Test assignment name 1" in the "user-grades" "table"
And I should not see "Test assignment name 2" in the "user-grades" "table"
And I should not see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | 170 |
And I click on grade item menu "Course 1" of type "course" on "grader" page
And I click on "Show grades only" "link"
And I should see "Test assignment name 1" in the "user-grades" "table"
And I should see "Test assignment name 2" in the "user-grades" "table"
And I should see "Manual grade"
And I should not see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 80 | 90 | 30 |
@javascript @skip_chrome_zerosize
Scenario: Minimise the grader report containing hidden activities without the 'moodle/grade:viewhidden' capability
Given I am on "Course 1" course homepage with editing mode on
And I open "Test assignment name 2" actions menu
And I choose "Hide" in the open action menu
And the following "role capability" exists:
| role | editingteacher |
| moodle/grade:viewhidden | prevent |
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
Then I should see "Test assignment name 1" in the "user-grades" "table"
And I should see "Test assignment name 2" in the "user-grades" "table"
And I should see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- | -6- |
| Student 1 | student1@example.com | 80 | - | 30 | 105.71 |
And I click on grade item menu "Course 1" of type "course" on "grader" page
And I choose "Show totals only" in the open action menu
And I should not see "Test assignment name 1" in the "user-grades" "table"
And I should not see "Test assignment name 2" in the "user-grades" "table"
And I should not see "Manual grade"
And I should see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | 105.71 |
And I click on grade item menu "Course 1" of type "course" on "grader" page
When I click on "Show grades only" "link"
Then I should see "Test assignment name 1" in the "user-grades" "table"
And I should see "Test assignment name 2" in the "user-grades" "table"
And I should see "Manual grade"
And I should not see "Course total"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- | -5- |
| Student 1 | student1@example.com | 80 | - | 30 |
@@ -0,0 +1,219 @@
@core @javascript @gradereport_grader
Feature: Within the grader report, test that we can open our generic filter dropdown component
In order to filter down the users on the page
As a teacher
I need to be able to see the filter and select a combination of parameters
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
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 | Dummy | User | student2@example.com | s2 |
| student3 | User | Example | student3@example.com | s3 |
| student4 | User | Test | student4@example.com | s4 |
| student5 | Turtle | Manatee | student5@example.com | s5 |
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 "activities" exist:
| activity | course | idnumber | name |
| assign | C1 | a1 | Test assignment one |
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
Scenario: A teacher can open the filter component
Given I should see "Filter by name"
When I click on "Filter by name" "combobox"
Then I should see "27" node occurrences of type "input" in the "First name" "core_grades > initials bar"
And I should see "27" node occurrences of type "input" in the "Last name" "core_grades > initials bar"
And "input[data-action=cancel]" "css_element" should exist
And "input[data-action=save]" "css_element" should exist
Scenario: A teacher can filter the grader report to limit users reported
Given I click on "Filter by name" "combobox"
And I wait until "input[data-action=save]" "css_element" exists
When I select "D" in the "First name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
# We should only have one user that matches the "D" first name
Then 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 |
# Test filtering on last name
# Business logic: If all is selected, we will not show it i.e. First (D) and NOT First (D) Last (All)
And I click on "First (D)" "combobox"
And I select "All" in the "First name" "core_grades > initials bar"
And I select "M" in the "Last name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
# We should only have one user that matches the "T" first name
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 |
# Test filtering on first && last name
And I click on "Last (M)" "combobox"
And I select "U" in the "First name" "core_grades > initials bar"
And I select "T" in the "Last name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
# We should only have one user that matches the "T" first name
And the following should exist in the "user-grades" table:
| -1- |
| User Test |
And the following should not exist in the "user-grades" table:
| -1- |
| Teacher 1 |
| Student 1 |
| User Example |
| Dummy User |
| Turtle Manatee |
# Final cheeky check to ensure our button matches.
And I click on "First (U) Last (T)" "combobox"
Scenario: A teacher can quickly tell that a filter is applied to the current table
Given I click on "Filter by name" "combobox"
And I wait until "input[data-action=save]" "css_element" exists
When I select "T" in the "First name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
Then I should see "First (T)"
# Check if removing the filter, removes the highlight and user notice of applied filters
And I click on "First (T)" "combobox"
And I wait until "input[data-action=save]" "css_element" exists
And I select "All" in the "First name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
# Check if the name button indicates if a filter is active
And I should see "Filter by name"
And I should not see "First (T)"
Scenario: A teacher can close the filter either by clicking close or clicking off the dropdown
Given I click on "Filter by name" "combobox"
And "input[data-action=save]" "css_element" should be visible
When I click on "input[data-action=cancel]" "css_element"
Then "input[data-action=save]" "css_element" should not be visible
# Click off the drop down
And I click on "Filter by name" "combobox"
And "input[data-action=save]" "css_element" should be visible
And I change window size to "large"
And I click on user profile field menu "fullname"
And "input[data-action=save]" "css_element" should not be visible
Scenario: A teacher using a language besides english can reset the initials bar
Given the following "language customisations" exist:
| component | stringid | value |
| core | all | |
And I click on "Filter by name" "combobox"
And "input[data-action=save]" "css_element" should be visible
And I select "T" in the "First name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
And I click on "First (T)" "combobox"
And I wait until "input[data-action=save]" "css_element" exists
When I select "" in the "First name" "core_grades > initials bar"
And I press "Apply"
And I wait to be redirected
Then I should not see "First () Last ()"
And the following should exist in the "user-grades" table:
| -1- |
| Dummy User |
| Student 1 |
| User Example |
| User Test |
| Turtle Manatee |
Scenario: A teacher can search and then filter by first or last name
Given I set the field "Search users" to "Student 1"
And I click on "Student 1" in the "user" search widget
And I click on "Filter by name" "combobox"
And I select "S" in the "First name" "core_grades > initials bar"
When I press "Apply"
And the field "Search users" matches value "Student 1"
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | - |
And the following should not exist in the "user-grades" table:
| -1- | -2- | -3- |
| Teacher 1 | teacher1@example.com | - |
| Dummy User | student2@example.com | - |
| User Example | student3@example.com | - |
| User Test | student4@example.com | - |
| Turtle Manatee | student5@example.com | - |
And I click on "First (S)" "combobox"
And I select "M" in the "First name" "core_grades > initials bar"
And I press "Apply"
And the following should not exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | - |
| Teacher 1 | teacher1@example.com | - |
| Dummy User | student2@example.com | - |
| User Example | student3@example.com | - |
| User Test | student4@example.com | - |
| Turtle Manatee | student5@example.com | - |
Scenario: A teacher can search for all users then filter with the initials bar
Given I set the field "Search users" to "User"
And I click on "View all results (3)" "option_role"
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| User Example | student3@example.com | - |
| User Test | student4@example.com | - |
| Dummy User | student2@example.com | - |
And the following should not exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | - |
| Teacher 1 | teacher1@example.com | - |
| Turtle Manatee | student5@example.com | - |
When I click on "Filter by name" "combobox"
And I select "E" in the "Last name" "core_grades > initials bar"
And I press "Apply"
Then the following should exist in the "user-grades" table:
| -1- | -2- | -3- |
| User Example | student3@example.com | - |
And the following should not exist in the "user-grades" table:
| -1- | -2- | -3- |
| Student 1 | student1@example.com | - |
| Teacher 1 | teacher1@example.com | - |
| Dummy User | student2@example.com | - |
| User Test | student4@example.com | - |
| Turtle Manatee | student5@example.com | - |
# This can be expanded for left/right/home & end keys but will have to be done in conjunction with the non mini render.
@accessibility
Scenario: A teacher can set focus and navigate the filter with the keyboard
Given 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
And I click on "Filter by name" "combobox"
And "input[data-action=save]" "css_element" should be visible
And the focused element is "All" "button" in the "First name" "core_grades > initials bar"
When I press the tab key
Then the focused element is "input[value=A]" "css_element" in the "First name" "core_grades > initials bar"
And I press the tab key
And the focused element is "input[value=B]" "css_element" in the "First name" "core_grades > initials bar"
@@ -0,0 +1,441 @@
@core @core_grades @javascript @gradereport @gradereport_grader
Feature: Within the grader report, test that we can search for users
In order to find specific users in the course gradebook
As a teacher
I need to be able to see the search input and trigger the search somehow
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 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 |
And the following "group members" exist:
| user | group |
| student5 | dg |
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" "grades > Grader report > View" page logged in as "teacher1"
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 "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 (1)" "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 (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 the grader 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- |
| Dummy User |
And the following should not exist in the "user-grades" 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 "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 |
# 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" 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 (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 "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 |
Scenario: A teacher can quickly tell that a search is active on the current table
When I click on "Turtle" in the "user" search widget
# 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" 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 "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
# 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 down key
And I press the enter key
And I wait "1" seconds
And the following should exist in the "user-grades" table:
| -1- |
| Student 1 |
And the following should not exist in the "user-grades" table:
| -1- |
| User Example |
| User Test |
| Dummy User |
| Turtle Manatee |
| 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"
And the focused element is "Search users" "field"
And I wait until "Turtle Manatee" "option_role" exists
# Basic tests for the page.
When 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
And 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 navigate to "View > Grader 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 ".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
# Sometimes with behat we get unattached nodes causing spurious failures.
And I wait "1" seconds
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 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"
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
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 "Filter by name" "combobox"
And I select "U" in the "First name" "core_grades > initials bar"
And I select "E" in the "Last name" "core_grades > initials bar"
And I press "Apply"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| User Example |
And the following should not exist in the "user-grades" 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" in "user" search within the gradebook widget does not exist
Scenario: A teacher can reset the search and filters all at once
Given I set the field "Search users" to "Turtle"
And I click on "Turtle Manatee" "option_role"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Turtle Manatee |
And I click on "Filter by name" "combobox"
And I select "T" in the "First name" "core_grades > initials bar"
And I select "M" in the "Last name" "core_grades > initials bar"
And the following should exist in the "user-grades" table:
| -1- |
| Turtle Manatee |
And I click on "Default group" in the "group" search widget
And the following should exist in the "user-grades" table:
| -1- |
| Turtle Manatee |
And I wait until the page is ready
When I click on "Clear all" "link" in the ".tertiary-navigation" "css_element"
And I wait until the page is ready
Then the field "Search users" matches value ""
Scenario: As a teacher I can dynamically find users whilst ignoring pagination
Given "42" "users" exist with the following data:
| username | students[count] |
| firstname | Student |
| lastname | s[count] |
| email | students[count]@example.com |
And "42" "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 "20"
When I set the field "Search users" to "42"
# One of the users' phone numbers also matches.
And I wait until "View all results (2)" "option_role" exists
Then I confirm "Student s42" in "user" search within the gradebook widget exists
Scenario: As a teacher I save grades using search and pagination
Given "42" "users" exist with the following data:
| username | students[count] |
| firstname | Student |
| lastname | test[count] |
| email | students[count]@example.com |
And "42" "course enrolments" exist with the following data:
| user | students[count] |
| course | C1 |
| role |student |
And I reload the page
And I turn editing mode on
And the field "perpage" matches value "20"
And I click on user profile field menu "fullname"
And I choose "Ascending" in the open action menu
And I wait until the page is ready
# Search for a single user on second page and save grades.
When I set the field "Search users" to "test32"
And I wait until "View all results (1)" "option_role" exists
And I click on "Student test32" "option_role"
And I wait until the page is ready
And I give the grade "80.00" to the user "Student test32" for the grade item "Test assignment one"
And I press "Save changes"
And I wait until the page is ready
Then the field "Search users" matches value "Student test32"
And the following should exist in the "user-grades" table:
| -1- |
| Student test32 |
And I set the field "Search users" to "test3"
And I click on "Student test31" "option_role"
And I wait until the page is ready
And I give the grade "70.00" to the user "Student test31" for the grade item "Test assignment one"
And I press "Save changes"
And I wait until the page is ready
Then the field "Search users" matches value "Student test31"
And the following should exist in the "user-grades" table:
| -1- |
| Student test31 |
And the following should not exist in the "user-grades" table:
| -1- |
| Student test32 |
And I click on "Clear" "link" in the ".user-search" "css_element"
And I wait until the page is ready
And the following should not exist in the "user-grades" table:
| -1- |
| Student test32 |
And I click on "2" "link" in the ".stickyfooter .pagination" "css_element"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
| -1- |
| Student test31 |
| Student test32 |
# Set grade for a single user on second page without search and save grades.
And I give the grade "70.00" to the user "Student test31" for the grade item "Test assignment one"
And I press "Save changes"
And I wait until the page is ready
# We are still on second page.
And the following should exist in the "user-grades" table:
| -1- |
| Student test31 |
| Student test32 |
# Search for multiple users on second page and save grades.
And I set the field "Search users" to "test3"
And I wait until "View all results (11)" "option_role" exists
And I click on "View all results (11)" "option_role"
And I wait until the page is ready
And I give the grade "10.00" to the user "Student test32" for the grade item "Test assignment one"
And I give the grade "20.00" to the user "Student test30" for the grade item "Test assignment one"
And I give the grade "30.00" to the user "Student test31" for the grade item "Test assignment one"
And I give the grade "40.00" to the user "Student test3" for the grade item "Test assignment one"
And I press "Save changes"
And I wait until the page is ready
Then the field "Search users" matches value "test3"
And the following should exist in the "user-grades" table:
| -1- |
| Student test3 |
| Student test30 |
| Student test31 |
| Student test32 |
And the following should not exist in the "user-grades" table:
| -1- |
| Student test1 |
| Student test2 |
| Student test4 |
@@ -0,0 +1,113 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for the gradereport_grader implementation of the privacy API.
*
* @package gradereport_grader
* @category test
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_grader\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\deletion_criteria;
use gradereport_grader\privacy\provider;
/**
* Unit tests for the gradereport_grader implementation of the privacy API.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp(): void {
$this->resetAfterTest(true);
}
/**
* Ensure that export_user_preferences returns no data if the user has no data.
*/
public function test_export_user_preferences_not_defined(): void {
$user = \core_user::get_user_by_username('admin');
provider::export_user_preferences($user->id);
$writer = writer::with_context(\context_system::instance());
$this->assertFalse($writer->has_any_data());
}
/**
* Ensure that export_user_preferences returns single preferences.
* These preferences can be set on each course, but the value is shared in the whole site.
*/
public function test_export_user_preferences_single(): void {
// Create test user, add some preferences.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
set_user_preference('grade_report_meanselection', GRADE_REPORT_MEAN_GRADED, $user);
set_user_preference('grade_report_studentsperpage', 50, $user);
// Switch to admin user (so we can validate preferences of our test user are still exported).
$this->setAdminUser();
// Validate exported data for our test user.
provider::export_user_preferences($user->id);
$context = \context_user::instance($user->id);
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$prefs = $writer->get_user_preferences('gradereport_grader');
$this->assertCount(2, (array) $prefs);
$this->assertEquals(get_string('meangraded', 'grades'), $prefs->grade_report_meanselection->value);
$this->assertEquals(50, $prefs->grade_report_studentsperpage->value);
}
/**
* Ensure that export_user_preferences returns preferences.
*/
public function test_export_user_preferences_multiple(): void {
// Create a course and add a user preference.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$course = $this->getDataGenerator()->create_course();
$collapsed = serialize(['aggregatesonly' => array(), 'gradesonly' => array()]);
set_user_preference('grade_report_grader_collapsed_categories'.$course->id, $collapsed, $user);
// Validate exported data.
provider::export_user_preferences($user->id);
$context = \context_user::instance($user->id);
/** @var \core_privacy\tests\request\content_writer $writer */
$writer = writer::with_context($context);
$this->assertTrue($writer->has_any_data());
$prefs = $writer->get_user_preferences('gradereport_grader');
$this->assertCount(1, (array) $prefs);
$this->assertEquals(
get_string('privacy:request:preference:grade_report_grader_collapsed_categories', 'gradereport_grader', ['name' => $course->fullname]),
$prefs->grade_report_grader_collapsed_categories->description
);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details for the grader report
*
* @package gradereport_grader
* @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'gradereport_grader'; // Full name of the plugin (used for diagnostics)