mirror of
https://github.com/moodle/moodle.git
synced 2025-04-20 16:04:25 +02:00
Merge branch 'MDL-77030-master-feedback' of https://github.com/kevpercy/moodle
This commit is contained in:
commit
7d750e2a46
File diff suppressed because one or more lines are too long
114
grade/classes/external/get_feedback.php
vendored
Normal file
114
grade/classes/external/get_feedback.php
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
<?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 core_grades\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use invalid_parameter_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
require_once($CFG->dirroot.'/grade/lib.php');
|
||||
|
||||
/**
|
||||
* Web service to fetch students feedback for a grade item.
|
||||
*
|
||||
* @package core_grades
|
||||
* @copyright 2023 Kevin Percy <kevin.percy@moodle.com>
|
||||
* @category external
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class get_feedback extends external_api {
|
||||
|
||||
/**
|
||||
* Returns description of method parameters.
|
||||
*
|
||||
* @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),
|
||||
'userid' => new external_value(PARAM_INT, 'User ID', VALUE_REQUIRED),
|
||||
'itemid' => new external_value(PARAM_INT, 'Grade Item ID', VALUE_REQUIRED)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user ID and grade item ID, return feedback and user details.
|
||||
*
|
||||
* @param int $courseid The course ID.
|
||||
* @param int $userid
|
||||
* @param int $itemid
|
||||
* @return array Feedback and user details
|
||||
*/
|
||||
public static function execute(int $courseid, int $userid, int $itemid): array {
|
||||
global $OUTPUT, $CFG;
|
||||
|
||||
$params = self::validate_parameters(
|
||||
self::execute_parameters(),
|
||||
[
|
||||
'courseid' => $courseid,
|
||||
'userid' => $userid,
|
||||
'itemid' => $itemid
|
||||
]
|
||||
);
|
||||
|
||||
$context = \context_course::instance($courseid);
|
||||
parent::validate_context($context);
|
||||
|
||||
require_capability('gradereport/grader:view', $context);
|
||||
|
||||
$gtree = new \grade_tree($params['courseid'], false, false, null, !$CFG->enableoutcomes);
|
||||
$gradeitem = $gtree->get_item($params['itemid']);
|
||||
|
||||
// If Item ID is not part of Course ID, $gradeitem will be set to false.
|
||||
if ($gradeitem === false) {
|
||||
throw new invalid_parameter_exception('Course ID and item ID mismatch');
|
||||
}
|
||||
|
||||
$grade = $gradeitem->get_grade($params['userid'], false);
|
||||
$user = \core_user::get_user($params['userid']);
|
||||
$extrafields = \core_user\fields::get_identity_fields($context);
|
||||
|
||||
return [
|
||||
'feedbacktext' => $grade->feedback,
|
||||
'title' => $gradeitem->get_name(true),
|
||||
'fullname' => fullname($user),
|
||||
'picture' => $OUTPUT->user_picture($user, ['size' => 35, 'link' => false]),
|
||||
'additionalfield' => empty($extrafields) ? '' : $user->{$extrafields[0]},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the return structure.
|
||||
*
|
||||
* @return external_single_structure
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'feedbacktext' => new external_value(PARAM_RAW, 'The full feedback text'),
|
||||
'title' => new external_value(PARAM_TEXT, 'Title of the grade item that the feedback is for'),
|
||||
'fullname' => new external_value(PARAM_TEXT, 'Students name'),
|
||||
'picture' => new external_value(PARAM_RAW, 'Students picture'),
|
||||
'additionalfield' => new external_value(PARAM_TEXT, 'Additional field for the user (email or ID number, for example)'),
|
||||
]);
|
||||
}
|
||||
}
|
@ -2464,6 +2464,12 @@ class grade_structure {
|
||||
if (isset($element['type']) && ($element['type'] == 'category')) {
|
||||
$class = 'category_grade_icons';
|
||||
}
|
||||
|
||||
if (!empty($grade->feedback) && $grade->load_grade_item()->gradetype != GRADE_TYPE_TEXT) {
|
||||
$statusicons .= $OUTPUT->pix_icon('i/asterisk', grade_helper::get_lang_string('feedbackprovided', 'grades'),
|
||||
'moodle', $attributes);
|
||||
}
|
||||
|
||||
if ($statusicons) {
|
||||
$statusicons = $OUTPUT->container($statusicons, $class);
|
||||
}
|
||||
@ -2489,6 +2495,8 @@ class grade_structure {
|
||||
$editable = true;
|
||||
|
||||
if ($element['type'] == 'grade') {
|
||||
$context->datatype = 'grade';
|
||||
|
||||
$item = $element['object']->grade_item;
|
||||
if ($item->is_course_item() || $item->is_category_item()) {
|
||||
$editable = (bool)get_config('moodle', 'grade_overridecat');;
|
||||
@ -2595,6 +2603,13 @@ class grade_structure {
|
||||
} else if ($element['type'] == 'userfield') {
|
||||
$context->dataid = $element['name'];
|
||||
}
|
||||
|
||||
if ($element['type'] != 'text' && !empty($element['object']->feedback)) {
|
||||
$viewfeedbackstring = grade_helper::get_lang_string('viewfeedback', 'grades');
|
||||
$context->viewfeedbackurl = html_writer::link('#', $viewfeedbackstring, ['class' => 'dropdown-item',
|
||||
'aria-label' => $viewfeedbackstring, 'role' => 'menuitem', 'data-action' => 'feedback',
|
||||
'data-courseid' => $this->courseid]);
|
||||
}
|
||||
} else if ($mode == 'user') {
|
||||
$context->datatype = 'user';
|
||||
$context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context, true);
|
||||
@ -2603,7 +2618,8 @@ class grade_structure {
|
||||
|
||||
if (!empty($USER->editing) || isset($context->gradeanalysisurl) || isset($context->gradesonlyurl)
|
||||
|| isset($context->aggregatesonlyurl) || isset($context->fullmodeurl) || isset($context->reporturl0)
|
||||
|| isset($context->ascendingfirstnameurl) || isset($context->ascendingurl) || ($mode == 'setup')) {
|
||||
|| isset($context->ascendingfirstnameurl) || isset($context->ascendingurl)
|
||||
|| isset($context->viewfeedbackurl) || ($mode == 'setup')) {
|
||||
return $OUTPUT->render_from_template('core_grades/cellmenu', $context);
|
||||
}
|
||||
return '';
|
||||
|
10
grade/report/grader/amd/build/feedback_modal.min.js
vendored
Normal file
10
grade/report/grader/amd/build/feedback_modal.min.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
define("gradereport_grader/feedback_modal",["exports","core/modal_factory","core/ajax","core/templates"],(function(_exports,_modal_factory,_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_factory=_interopRequireDefault(_modal_factory),_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)=>{const feedbackData=await fetchFeedback(courseid,userid,itemid);_modal_factory.default.create({removeOnClose:!0,large:!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)}}))};_exports.init=()=>{registerEventListeners()}}));
|
||||
|
||||
//# sourceMappingURL=feedback_modal.min.js.map
|
1
grade/report/grader/amd/build/feedback_modal.min.js.map
Normal file
1
grade/report/grader/amd/build/feedback_modal.min.js.map
Normal file
@ -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 ModalFactory from 'core/modal_factory';\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 const feedbackData = await fetchFeedback(courseid, userid, itemid);\n\n return ModalFactory.create({\n removeOnClose: true,\n large: 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 }\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","ModalFactory","create","removeOnClose","large","then","modal","body","Templates","render","feedbacktext","user","picture","fullname","additionalfield","setBody","setTitle","title","show","getModal","dataset","uid"],"mappings":";;;;;;;gOA0BMA,uBACY,2BA4CZC,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,iBApDGC,OAAMhB,SAAUC,OAAQC,gBAC/Be,mBAAqBlB,cAAcC,SAAUC,OAAQC,QAEpDgB,uBAAaC,OAAO,CACvBC,eAAe,EACfC,OAAO,IAEVC,MAAKC,cACIC,KAAOC,mBAAUC,OAAO,6BAA8B,CACxDC,aAAcV,aAAaU,aAC3BC,KAAM,CACFC,QAASZ,aAAaY,QACtBC,SAAUb,aAAaa,SACvBC,gBAAiBd,aAAac,0BAItCR,MAAMS,QAAQR,MACdD,MAAMU,SAAShB,aAAaiB,OAC5BX,MAAMY,OAECZ,UAqCHa,CAJiBxB,oBAAoByB,QAAQrC,SAC9BW,EAAEE,OAAOC,QAAQ,MAAMuB,QAAQC,IAC/B3B,EAAEE,OAAOC,QAAQ,MAAMuB,QAAQnC,2BAUtC,KAChBM"}
|
107
grade/report/grader/amd/src/feedback_modal.js
Normal file
107
grade/report/grader/amd/src/feedback_modal.js
Normal file
@ -0,0 +1,107 @@
|
||||
// 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 ModalFactory from 'core/modal_factory';
|
||||
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) => {
|
||||
const feedbackData = await fetchFeedback(courseid, userid, itemid);
|
||||
|
||||
return ModalFactory.create({
|
||||
removeOnClose: true,
|
||||
large: 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize module
|
||||
*/
|
||||
export const init = () => {
|
||||
registerEventListeners();
|
||||
};
|
@ -50,6 +50,7 @@ $PAGE->set_url(new moodle_url('/grade/report/grader/index.php', array('id'=>$cou
|
||||
$PAGE->set_pagelayout('report');
|
||||
$PAGE->requires->js_call_amd('gradereport_grader/stickycolspan', 'init');
|
||||
$PAGE->requires->js_call_amd('gradereport_grader/search', 'init');
|
||||
$PAGE->requires->js_call_amd('gradereport_grader/feedback_modal', 'init');
|
||||
|
||||
// basic access checks
|
||||
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
|
||||
|
@ -1117,7 +1117,12 @@ class grade_report_grader extends grade_report {
|
||||
}
|
||||
}
|
||||
|
||||
if (!$item->needsupdate) {
|
||||
if ($item->gradetype == GRADE_TYPE_TEXT && !empty($grade->feedback)) {
|
||||
$context->text = html_writer::span(shorten_text(strip_tags($grade->feedback), 20), '',
|
||||
['data-action' => 'feedback', 'role' => 'button', 'data-courseid' => $this->courseid]);
|
||||
}
|
||||
|
||||
if (!$item->needsupdate && !($item->gradetype == GRADE_TYPE_TEXT && empty($USER->editing))) {
|
||||
$context->actionmenu = $this->gtree->get_cell_action_menu($element, 'gradeitem', $this->gpr);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
"descendingfirstnameurl": "<a class='dropdown-item' aria-label='Descending' role='menuitem' href='index.php?id=13&sortitemid=firstname&sort=desc&gpr_type=report&gpr_plugin=grader&gpr_courseid=13'>Descending</a>",
|
||||
"ascendinglastnameurl": "<a class='dropdown-item' aria-label='Ascending' role='menuitem' href='index.php?id=13&sortitemid=lastname&sort=asc&gpr_type=report&gpr_plugin=grader&gpr_courseid=13'>Ascending</a>",
|
||||
"descendinglastnameurl": "<a class='dropdown-item' aria-label='Descending' role='menuitem' href='index.php?id=13&sortitemid=lastname&sort=desc&gpr_type=report&gpr_plugin=grader&gpr_courseid=13'>Descending</a>",
|
||||
"viewfeedbackurl": "<a href='#' class='dropdown-item' aria-label='View feedback' role='menuitem'>View feedback</a>",
|
||||
"divider1": "true",
|
||||
"divider2": "true",
|
||||
"datatype": "item",
|
||||
@ -78,6 +79,7 @@
|
||||
{{#hideurl}}{{{hideurl}}}{{/hideurl}}
|
||||
{{#lockurl}}{{{lockurl}}}{{/lockurl}}
|
||||
{{#resetweightsurl}}{{{resetweightsurl}}}{{/resetweightsurl}}
|
||||
{{#viewfeedbackurl}}{{{viewfeedbackurl}}}{{/viewfeedbackurl}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
64
grade/templates/feedback_modal.mustache
Normal file
64
grade/templates/feedback_modal.mustache
Normal file
@ -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 core_grades/feedback_modal
|
||||
|
||||
The body of the modal displayed when viewing feedback for a grade item.
|
||||
|
||||
Context variables required for this template:
|
||||
* feedbacktext - The full text of the feedback.
|
||||
* user - Object containing information about the student.
|
||||
* picture - The image corresponding to the user.
|
||||
* fullname - The users full name.
|
||||
* additionalfield - Additional text displayed below the users full name (e.g. the users email address)
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"feedbacktext": "<p>Example feedback text</p>",
|
||||
"user": {
|
||||
"picture": "<img src=\"http://example.com/pluginfile.php/14/user/icon/boost/f2\" class=\"userpicture\" width=\"40\" alt=\"\">",
|
||||
"fullname": "John Doe",
|
||||
"additionalfield": "johndoe@example.com"
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
{{#user}}
|
||||
<div class="d-flex p-3">
|
||||
<div class="d-block pr-3">
|
||||
{{#picture}}
|
||||
{{{picture}}}
|
||||
{{/picture}}
|
||||
{{^picture}}
|
||||
<span class="userinitials"></span>
|
||||
{{/picture}}
|
||||
</div>
|
||||
<div class="d-block">
|
||||
<span class="p-0">
|
||||
{{fullname}}
|
||||
</span>
|
||||
{{#additionalfield}}
|
||||
<span class="d-block small">
|
||||
{{additionalfield}}
|
||||
</span>
|
||||
{{/additionalfield}}
|
||||
</div>
|
||||
</div>
|
||||
{{/user}}
|
||||
<div class="box p-3">
|
||||
{{{feedbacktext}}}
|
||||
</div>
|
@ -38,6 +38,10 @@ class behat_grades extends behat_base {
|
||||
'initials bar',
|
||||
[".//*[contains(concat(' ', @class, ' '), ' initialbar ')]//span[contains(., %locator%)]/parent::div"]
|
||||
),
|
||||
new behat_component_named_selector(
|
||||
'grade_actions',
|
||||
["//td[count(//table[@id='user-grades']//th[contains(., %locator%)]/preceding-sibling::th)]//*[@data-type='grade']"]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
69
grade/tests/behat/grade_feedback.feature
Normal file
69
grade/tests/behat/grade_feedback.feature
Normal file
@ -0,0 +1,69 @@
|
||||
@gradereport @gradereport_grader @javascript
|
||||
Feature: Display feedback on the Grader report
|
||||
In order to check the expected results are displayed
|
||||
As a teacher
|
||||
I need to see the feedback information in a modal
|
||||
|
||||
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 |
|
||||
| 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 | assignfeedback_comments_enabled |
|
||||
| assign | C1 | 1 | Test assignment name 1 | Submit your online text | 1 |
|
||||
| assign | C1 | 1 | Test assignment name 2 | submit your online text | 1 |
|
||||
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 | course | gradetype | itemtype |
|
||||
| Grade item 1 | C1 | text | manual |
|
||||
And the following "grade grades" exist:
|
||||
| gradeitem | user | grade | feedback |
|
||||
| Grade item 1 | student1 | | Longer feedback text content |
|
||||
And I log in as "teacher1"
|
||||
And I am on the "Test assignment name 1" "assign activity" page
|
||||
And I follow "View all submissions"
|
||||
And I click on "Grade" "link" in the "Student 1" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Grade out of 100 | 50 |
|
||||
| Feedback comments | This is feedback |
|
||||
And I press "Save changes"
|
||||
|
||||
Scenario: View the feedback icon on the Grader report
|
||||
Given I am on "Course 1" course homepage
|
||||
When I navigate to "View > Grader report" in the course gradebook
|
||||
Then I should see "Test assignment name 1"
|
||||
And I should see "Test assignment name 2"
|
||||
And "Feedback provided" "icon" should exist in the "Student 1" "table_row"
|
||||
And "Feedback provided" "icon" should not exist in the "Student 2" "table_row"
|
||||
|
||||
Scenario: View the feedback modal from the action menu
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I click on "Test assignment name 1" "core_grades > grade_actions" in the "Student 1" "table_row"
|
||||
When I choose "View feedback" in the open action menu
|
||||
Then I should see "This is feedback" in the "Test assignment name 1" "dialogue"
|
||||
|
||||
Scenario: View the feedback text for text only grade
|
||||
Given I am on "Course 1" course homepage
|
||||
When I navigate to "View > Grader report" in the course gradebook
|
||||
Then I should see "Grade item 1"
|
||||
And "Longer feedback ..." "text" should exist in the "Student 1" "table_row"
|
||||
|
||||
Scenario: View the feedback modal for text only grade
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
When I click on "Longer feedback ..." "text" in the "Student 1" "table_row"
|
||||
Then I should see "Longer feedback text content" in the "Grade item 1" "dialogue"
|
179
grade/tests/external/get_feedback_test.php
vendored
Normal file
179
grade/tests/external/get_feedback_test.php
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
<?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 core_grades\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
/**
|
||||
* Unit tests for the core_grades\external\get_feedback webservice.
|
||||
*
|
||||
* @package core_grades
|
||||
* @category external
|
||||
* @copyright 2023 Kevin Percy <kevin.percy@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
class get_feedback_test extends \externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test get_feedback.
|
||||
*
|
||||
* @covers ::get_feedback
|
||||
* @dataProvider get_feedback_provider
|
||||
* @param string|null $feedback The feedback text added for the grade item.
|
||||
* @param array $expected The expected feedback data.
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_feedback(?string $feedback, array $expected) {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_user(['firstname' => 'John', 'lastname' => 'Doe',
|
||||
'email' => 'johndoe@example.com']);
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
||||
$gradeitem = $this->getDataGenerator()->create_grade_item(['itemname' => 'Grade item 1',
|
||||
'courseid' => $course->id]);
|
||||
|
||||
$gradegradedata = [
|
||||
'itemid' => $gradeitem->id,
|
||||
'userid' => $user->id,
|
||||
];
|
||||
|
||||
if ($feedback) {
|
||||
$gradegradedata['feedback'] = $feedback;
|
||||
}
|
||||
|
||||
$this->getDataGenerator()->create_grade_grade($gradegradedata);
|
||||
$this->setAdminUser();
|
||||
|
||||
$feedbackdata = get_feedback::execute($course->id, $user->id, $gradeitem->id);
|
||||
|
||||
$this->assertEquals($expected['feedbacktext'], $feedbackdata['feedbacktext']);
|
||||
$this->assertEquals($expected['title'], $feedbackdata['title']);
|
||||
$this->assertEquals($expected['fullname'], $feedbackdata['fullname']);
|
||||
$this->assertEquals($expected['additionalfield'], $feedbackdata['additionalfield']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_feedback().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_feedback_provider(): array {
|
||||
return [
|
||||
'Return when feedback is set.' => [
|
||||
'Test feedback',
|
||||
[
|
||||
'feedbacktext' => 'Test feedback',
|
||||
'title' => 'Grade item 1',
|
||||
'fullname' => 'John Doe',
|
||||
'additionalfield' => 'johndoe@example.com'
|
||||
]
|
||||
],
|
||||
'Return when feedback is not set.' => [
|
||||
null,
|
||||
[
|
||||
'feedbacktext' => null,
|
||||
'title' => 'Grade item 1',
|
||||
'fullname' => 'John Doe',
|
||||
'additionalfield' => 'johndoe@example.com'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_feedback with invalid requests.
|
||||
*
|
||||
* @covers ::get_feedback
|
||||
* @dataProvider get_feedback_invalid_request_provider
|
||||
* @param string $loggeduserrole The role of the logged user.
|
||||
* @param bool $feedbacknotincourse Whether to request a feedback for a grade item which is not a part of the course.
|
||||
* @param array $expectedexception The expected exception.
|
||||
* @return void
|
||||
*/
|
||||
public function test_get_feedback_invalid_request(string $loggeduserrole, bool $feedbacknotincourse,
|
||||
array $expectedexception = []) {
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
// Create a course with a user and a grade item.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
||||
$gradeitem = $this->getDataGenerator()->create_grade_item(['courseid' => $course->id]);
|
||||
// Add feedback for the grade item in course.
|
||||
$gradegradedata = [
|
||||
'itemid' => $gradeitem->id,
|
||||
'userid' => $user->id,
|
||||
'feedback' => 'Test feedback',
|
||||
];
|
||||
|
||||
$this->getDataGenerator()->create_grade_grade($gradegradedata);
|
||||
// Set the current user as specified.
|
||||
if ($loggeduserrole === 'user') {
|
||||
$this->setUser($user);
|
||||
} else if ($loggeduserrole === 'guest') {
|
||||
$this->setGuestUser();
|
||||
} else {
|
||||
$this->setAdminUser();
|
||||
}
|
||||
|
||||
if ($feedbacknotincourse) { // Create a new course which will be later used in the feedback request call.
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
}
|
||||
|
||||
$this->expectException($expectedexception['exceptionclass']);
|
||||
|
||||
if (!empty($expectedexception['exceptionmessage'])) {
|
||||
$this->expectExceptionMessage($expectedexception['exceptionmessage']);
|
||||
}
|
||||
|
||||
get_feedback::execute($course->id, $user->id, $gradeitem->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_feedback_invalid_request().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_feedback_invalid_request_provider(): array {
|
||||
return [
|
||||
'Logged user does not have permissions to view feedback.' => [
|
||||
'user',
|
||||
false,
|
||||
['exceptionclass' => \required_capability_exception::class]
|
||||
],
|
||||
'Guest user cannot view feedback.' => [
|
||||
'guest',
|
||||
false,
|
||||
['exceptionclass' => \require_login_exception::class]
|
||||
],
|
||||
'Request feedback for a grade item which is not a part of the course.' => [
|
||||
'admin',
|
||||
true,
|
||||
[
|
||||
'exceptionclass' => \invalid_parameter_exception::class,
|
||||
'exceptionmessage' => 'Course ID and item ID mismatch',
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
@ -232,6 +232,7 @@ $string['feedbackedit'] = 'Edit feedback';
|
||||
$string['feedbackfiles'] = 'Feedback files';
|
||||
$string['feedbackforgradeitems'] = 'Feedback for {$a}';
|
||||
$string['feedbackhistoryfiles'] = 'Feedback history files';
|
||||
$string['feedbackprovided'] = 'Feedback provided';
|
||||
$string['feedbacks'] = 'Feedbacks';
|
||||
$string['feedbacksaved'] = 'Feedback saved';
|
||||
$string['feedbackview'] = 'View feedback';
|
||||
@ -865,6 +866,7 @@ $string['verbosescales'] = 'Verbose scales';
|
||||
$string['verbosescales_help'] = 'A verbose scale uses words rather than numbers. Set to \'Yes\' if both numerical and verbose scales are to be imported. Set to \'No\' if only numerical scales are to be imported.';
|
||||
$string['viewas'] = 'View report as';
|
||||
$string['viewbygroup'] = 'Group';
|
||||
$string['viewfeedback'] = 'View feedback';
|
||||
$string['viewgrades'] = 'View grades';
|
||||
$string['weight'] = 'weight';
|
||||
$string['weight_help'] = 'A value used to determine the relative value of multiple grade items in a category or course.';
|
||||
|
@ -195,6 +195,7 @@ class icon_system_fontawesome extends icon_system_font {
|
||||
'theme:fp/view_tree_active' => 'fa-folder',
|
||||
'core:i/addblock' => 'fa-plus-square',
|
||||
'core:i/assignroles' => 'fa-user-plus',
|
||||
'core:i/asterisk' => 'fa-asterisk',
|
||||
'core:i/backup' => 'fa-file-zip-o',
|
||||
'core:i/badge' => 'fa-shield',
|
||||
'core:i/breadcrumbdivider' => 'fa-angle-right',
|
||||
|
@ -995,6 +995,12 @@ $functions = array(
|
||||
'ajax' => true,
|
||||
'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
|
||||
],
|
||||
'core_grades_get_feedback' => [
|
||||
'classname' => 'core_grades\external\get_feedback',
|
||||
'description' => 'Get the feedback data for a grade item',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_grading_get_definitions' => array(
|
||||
'classname' => 'core_grading_external',
|
||||
'methodname' => 'get_definitions',
|
||||
|
BIN
pix/i/asterisk.png
Normal file
BIN
pix/i/asterisk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 360 B |
2
pix/i/asterisk.svg
Normal file
2
pix/i/asterisk.svg
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1546 1050q46 26 59.5 77.5t-12.5 97.5l-64 110q-26 46-77.5 59.5t-97.5-12.5l-266-153v307q0 52-38 90t-90 38h-128q-52 0-90-38t-38-90v-307l-266 153q-46 26-97.5 12.5t-77.5-59.5l-64-110q-26-46-12.5-97.5t59.5-77.5l266-154-266-154q-46-26-59.5-77.5t12.5-97.5l64-110q26-46 77.5-59.5t97.5 12.5l266 153v-307q0-52 38-90t90-38h128q52 0 90 38t38 90v307l266-153q46-26 97.5-12.5t77.5 59.5l64 110q26 46 12.5 97.5t-59.5 77.5l-266 154z" fill="#999"/></svg>
|
After Width: | Height: | Size: 574 B |
Loading…
x
Reference in New Issue
Block a user