Merge branch 'MDL-77030-master-feedback' of https://github.com/kevpercy/moodle

This commit is contained in:
Sara Arjona 2023-04-11 11:17:52 +02:00
commit 7d750e2a46
18 changed files with 586 additions and 3 deletions

File diff suppressed because one or more lines are too long

114
grade/classes/external/get_feedback.php vendored Normal file
View 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)'),
]);
}
}

View File

@ -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 '';

View 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

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

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@
"descendingfirstnameurl": "<a class='dropdown-item' aria-label='Descending' role='menuitem' href='index.php?id=13&amp;sortitemid=firstname&amp;sort=desc&amp;gpr_type=report&amp;gpr_plugin=grader&amp;gpr_courseid=13'>Descending</a>",
"ascendinglastnameurl": "<a class='dropdown-item' aria-label='Ascending' role='menuitem' href='index.php?id=13&amp;sortitemid=lastname&amp;sort=asc&amp;gpr_type=report&amp;gpr_plugin=grader&amp;gpr_courseid=13'>Ascending</a>",
"descendinglastnameurl": "<a class='dropdown-item' aria-label='Descending' role='menuitem' href='index.php?id=13&amp;sortitemid=lastname&amp;sort=desc&amp;gpr_type=report&amp;gpr_plugin=grader&amp;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>

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

View File

@ -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']"]
),
];
}

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

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

View File

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

View File

@ -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',

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

2
pix/i/asterisk.svg Normal file
View 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