MDL-77033 core_grades: Add status column to setup page

Also make action menu consistent with grader report
This commit is contained in:
Ilya Tregubov 2023-03-06 11:00:19 +08:00
parent 7653093b55
commit 11b6dce50c
9 changed files with 288 additions and 75 deletions

View file

@ -247,7 +247,7 @@ if (grade_regrade_final_grades_if_required($course, $grade_edit_tree_index_check
$actionbar = new \core_grades\output\gradebook_setup_action_bar($context);
print_grade_page_head($courseid, 'settings', 'setup', get_string('gradebooksetup', 'grades'),
false, false, true, null, null, null, $actionbar);
false, false, true, null, null, null, $actionbar, false);
// Print Table of categories and items
echo $OUTPUT->box_start('gradetreebox generalbox');
@ -292,5 +292,3 @@ $PAGE->requires->js_call_amd('core_form/changechecker', 'watchFormById', ['grade
echo $OUTPUT->footer();
die;

View file

@ -66,6 +66,7 @@ class grade_edit_tree {
}
$this->columns[] = grade_edit_tree_column::factory('range'); // This is not a setting... How do we deal with it?
$this->columns[] = grade_edit_tree_column::factory('status');
$this->columns[] = grade_edit_tree_column::factory('actions');
if ($this->deepest_level > 1) {
@ -104,7 +105,7 @@ class grade_edit_tree {
$object = $element['object'];
$eid = $element['eid'];
$object->name = $this->gtree->get_element_header($element, true, false, true, true, true);
$object->name = $this->gtree->get_element_header($element, true, false, true, false, true);
$object->icon = $this->gtree->get_element_icon($element);
$object->type = $this->gtree->get_element_type_string($element);
$object->stripped_name = $this->gtree->get_element_header($element, false, false, false);
@ -119,54 +120,13 @@ class grade_edit_tree {
}
$moveaction = '';
$actionsmenu = new action_menu();
$actionsmenu->set_menu_trigger($OUTPUT->pix_icon('i/moremenu', get_string('actions')), 'actions');
$actionsmenu->set_owner_selector('grade-item-' . $eid);
if (!$is_category_item && ($icon = $this->gtree->get_edit_icon($element, $this->gpr, true))) {
$actionsmenu->add($icon);
}
// MDL-49281 if grade_item already has calculation, it should be editable even if global setting is off.
$type = $element['type'];
$iscalculated = ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') && $object->is_calculated();
$icon = $this->gtree->get_calculation_icon($element, $this->gpr, true);
if ($iscalculated || $icon) {
$actionsmenu->add($icon);
}
$actions = $this->gtree->get_cell_action_menu($element, 'setup', $this->gpr);
if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) {
if ($this->element_deletable($element)) {
$aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey()));
$icon = new action_menu_link_secondary($aurl, new pix_icon('t/delete', get_string('delete')), get_string('delete'));
$actionsmenu->add($icon);
}
if ($this->element_duplicatable($element)) {
$duplicateparams = array();
$duplicateparams['id'] = $COURSE->id;
$duplicateparams['action'] = 'duplicate';
$duplicateparams['eid'] = $eid;
$duplicateparams['sesskey'] = sesskey();
$aurl = new moodle_url('index.php', $duplicateparams);
$duplicateicon = new pix_icon('t/copy', get_string('duplicate'));
$icon = new action_menu_link_secondary($aurl, $duplicateicon, get_string('duplicate'));
$actionsmenu->add($icon);
}
$aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
$moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
}
if ($icon = $this->gtree->get_hiding_icon($element, $this->gpr, true)) {
$actionsmenu->add($icon);
}
if ($icon = $this->gtree->get_reset_icon($element, $this->gpr, true)) {
$actionsmenu->add($icon);
}
$actions = $OUTPUT->render($actionsmenu);
$returnrows = array();
$root = false;
@ -205,8 +165,6 @@ class grade_edit_tree {
$item = $category->get_grade_item();
// Add aggregation coef input if not a course item and if parent category has correct aggregation type
$dimmed = ($item->is_hidden()) ? 'dimmed_text' : '';
// Before we print the category's row, we must find out how many rows will appear below it (for the filler cell's rowspan)
$aggregation_position = grade_get_setting($COURSE->id, 'aggregationposition', $CFG->grade_aggregationposition);
$category_total_data = null; // Used if aggregationposition is set to "last", so we can print it last
@ -315,7 +273,7 @@ class grade_edit_tree {
$categoryrow = new html_table_row();
$categoryrow->id = 'grade-item-' . $eid;
$categoryrow->attributes['class'] = $courseclass . ' category ' . $dimmed;
$categoryrow->attributes['class'] = $courseclass . ' category ';
$categoryrow->attributes['data-category'] = $eid;
$categoryrow->attributes['data-itemid'] = $category->get_grade_item()->id;
$categoryrow->attributes['data-hidden'] = 'false';
@ -373,10 +331,9 @@ class grade_edit_tree {
$categoryitemclass = 'courseitem';
}
$dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
$gradeitemrow = new html_table_row();
$gradeitemrow->id = 'grade-item-' . $eid;
$gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
$gradeitemrow->attributes['class'] = $categoryitemclass . ' item ';
$gradeitemrow->attributes['data-itemid'] = $object->id;
$gradeitemrow->attributes['data-hidden'] = 'false';
// If this item is a course or category aggregation, add a data attribute that stores the identifier of
@ -486,7 +443,7 @@ class grade_edit_tree {
* @param array $element
* @return bool
*/
function element_deletable($element) {
public static function element_deletable($element) {
global $COURSE;
if ($element['type'] != 'item') {
@ -514,7 +471,7 @@ class grade_edit_tree {
* @param array $element
* @return bool
*/
public function element_duplicatable($element) {
public static function element_duplicatable($element) {
if ($element['type'] != 'item') {
return false;
}
@ -1010,6 +967,86 @@ class grade_edit_tree_column_range extends grade_edit_tree_column {
}
}
/**
* Class grade_edit_tree_column_status
*
* @package core_grades
* @copyright 2023 Ilya Tregubov <ilya@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class grade_edit_tree_column_status extends grade_edit_tree_column {
/**
* Get status column header cell
* @return html_table_cell status column header cell
*/
public function get_header_cell() {
$headercell = clone($this->headercell);
$headercell->text = get_string('status');
return $headercell;
}
/**
* Get category cell in status column
*
* @param grade_category $category grade category
* @param string $levelclass Category level info
* @param array $params Params (category id, action performed etc)
* @return html_table_cell category cell in status columns
*/
public function get_category_cell($category, $levelclass, $params) {
global $OUTPUT, $gtree;
$categorycell = parent::get_category_cell($category, $levelclass, $params);
$element = [];
$element['object'] = $category;
$categorycell->text = $gtree->set_grade_status_icons($element);
// Aggregation type.
$aggrstrings = grade_helper::get_aggregation_strings();
$context = new stdClass();
$context->aggregation = $aggrstrings[$category->aggregation];
// Include/exclude empty grades.
if ($category->aggregateonlygraded) {
$context->aggregateonlygraded = $category->aggregateonlygraded;
}
// Aggregate outcomes.
if ($category->aggregateoutcomes) {
$context->aggregateoutcomes = $category->aggregateoutcomes;
}
// Drop the lowest.
if ($category->droplow) {
$context->droplow = $category->droplow;
}
// Keep the highest.
if ($category->keephigh) {
$context->keephigh = $category->keephigh;
}
$categorycell->text .= $OUTPUT->render_from_template('core_grades/category_settings', $context);
return $categorycell;
}
/**
* Get category cell in status column
*
* @param grade_item $item grade item
* @param array $params Params
* @return html_table_cell item cell in status columns
*/
public function get_item_cell($item, $params) {
global $gtree;
$element = [];
$element['object'] = $item;
$itemcell = parent::get_item_cell($item, $params);
$itemcell->text = $gtree->set_grade_status_icons($element);
return $itemcell;
}
}
/**
* Class grade_edit_tree_column_actions
*

View file

@ -1858,6 +1858,81 @@ class grade_structure {
}
}
/**
* Returns a link to reset weights for the given element.
*
* @param array $element An array representing an element in the grade_tree
* @param object $gpr A grade_plugin_return object
* @return string|null
*/
public function get_reset_weights_link(array $element, object $gpr): ?string {
// Limit to category items set to use the natural weights aggregation method, and users
// with the capability to manage grades.
if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM ||
!has_capability('moodle/grade:manage', $this->context)) {
return null;
}
$title = grade_helper::get_lang_string('resetweightsshort', 'grades');
$str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element));
$url = new moodle_url('/grade/edit/tree/action.php', [
'id' => $this->courseid,
'action' => 'resetweights',
'eid' => $element['eid'],
'sesskey' => sesskey(),
]);
$gpr->add_url_params($url);
return html_writer::link($url, $title,
['class' => 'dropdown-item', 'aria-label' => $str, 'role' => 'menuitem']);
}
/**
* Returns a link to delete a given element.
*
* @param array $element An array representing an element in the grade_tree
* @param object $gpr A grade_plugin_return object
* @return string|null
*/
public function get_delete_link(array $element, object $gpr): ?string {
if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) {
if (grade_edit_tree::element_deletable($element)) {
$url = new moodle_url('index.php',
['id' => $this->courseid, 'action' => 'delete', 'eid' => $element['eid'], 'sesskey' => sesskey()]);
$title = grade_helper::get_lang_string('delete');
$gpr->add_url_params($url);
return html_writer::link($url, $title,
['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']);
}
}
return null;
}
/**
* Returns a link to duplicate a given element.
*
* @param array $element An array representing an element in the grade_tree
* @param object $gpr A grade_plugin_return object
* @return string|null
*/
public function get_duplicate_link(array $element, object $gpr): ?string {
if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) {
if (grade_edit_tree::element_duplicatable($element)) {
$duplicateparams = [];
$duplicateparams['id'] = $this->courseid;
$duplicateparams['action'] = 'duplicate';
$duplicateparams['eid'] = $element['eid'];
$duplicateparams['sesskey'] = sesskey();
$url = new moodle_url('index.php', $duplicateparams);
$title = grade_helper::get_lang_string('duplicate');
$gpr->add_url_params($url);
return html_writer::link($url, $title,
['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']);
}
}
return null;
}
/**
* Return edit icon for give element
*
@ -2384,7 +2459,7 @@ class grade_structure {
$context = new stdClass();
if ($mode == 'gradeitem') {
if ($mode == 'gradeitem' || $mode == 'setup') {
$editable = true;
if ($element['type'] == 'grade') {
@ -2408,17 +2483,23 @@ class grade_structure {
$context->datatype = 'item';
if ($element['type'] == 'item') {
if ($mode == 'setup') {
$context->deleteurl = $this->get_delete_link($element, $gpr);
$context->duplicateurl = $this->get_duplicate_link($element, $gpr);
} else {
$context =
grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context, true);
grade_report::get_additional_context($this->context, $this->courseid,
$element, $gpr, $mode, $context, true);
$context->advancedgradingurl = $this->get_advanced_grading_link($element, $gpr);
}
}
if ($element['type'] == 'item') {
$context->divider1 = true;
}
if (!empty($USER->editing)) {
if ($element['type'] !== 'userfield') {
if (!empty($USER->editing) || $mode == 'setup') {
if (($element['type'] !== 'userfield') && ($mode !== 'setup')) {
$context->divider1 = true;
$context->divider2 = true;
}
@ -2464,15 +2545,24 @@ class grade_structure {
}
} else if ($element['type'] == 'category') {
$context->datatype = 'category';
if ($mode !== 'setup') {
$mode = 'category';
$context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context);
if (!empty($USER->editing)) {
$context = grade_report::get_additional_context($this->context, $this->courseid,
$element, $gpr, $mode, $context);
} else {
$context->deleteurl = $this->get_delete_link($element, $gpr);
$context->resetweightsurl = $this->get_reset_weights_link($element, $gpr);
}
if (!empty($USER->editing) || $mode == 'setup') {
if ($mode !== 'setup') {
$context->divider1 = true;
}
$context->editurl = $this->get_edit_link($element, $gpr);
$context->hideurl = $this->get_hiding_link($element, $gpr);
$context->lockurl = $this->get_locking_link($element, $gpr);
}
}
if (isset($element['object'])) {
$context->dataid = $element['object']->id;
@ -2487,7 +2577,7 @@ 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)) {
|| isset($context->ascendingfirstnameurl) || isset($context->ascendingurl) || ($mode == 'setup')) {
return $OUTPUT->render_from_template('core_grades/cellmenu', $context);
}
return '';
@ -3801,4 +3891,3 @@ abstract class grade_helper {
self::$aggregationstrings = null;
}
}

View file

@ -0,0 +1,56 @@
{{!
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/cellmenu
This template renders action menu for a given cell.
Example context (json):
{
"aggregation": "Natural",
"aggregateonlygraded": 1,
"aggregateoutcomes": 0,
"droplow": 1,
"keephigh": 1
}
}}
<div class="card-deck" role="list">
{{#aggregation}}
<span class="badge badge-pill badge-light" role="listitem">
<strong>{{aggregation}}</strong>
</span>
{{/aggregation}}
{{#aggregateonlygraded}}
<span class="badge badge-pill badge-light" role="listitem">
<strong>{{#str}}aggregateonlygraded, grades{{/str}}</strong>
</span>
{{/aggregateonlygraded}}
{{#aggregateoutcomes}}
<span class="badge badge-pill badge-light" role="listitem">
<strong>{{#str}}aggregateoutcomes, grades{{/str}}</strong>
</span>
{{/aggregateoutcomes}}
{{#droplow}}
<span class="badge badge-pill badge-light" role="listitem">
<strong>{{#str}}droplowestvalues, grades, {{droplow}}{{/str}}</strong>
</span>
{{/droplow}}
{{#keephigh}}
<span class="badge badge-pill badge-light" role="listitem">
<strong>{{#str}}keephigh, grades{{/str}}</strong>
</span>
{{/keephigh}}
</div>

View file

@ -48,14 +48,15 @@
<div role="menu" class="dropdown-menu">
{{#editurl}}{{{editurl}}}{{/editurl}}
{{#gradeanalysisurl}}{{{gradeanalysisurl}}}{{/gradeanalysisurl}}
{{#editcalculationurl}}{{{editcalculationurl}}}{{/editcalculationurl}}
{{#reporturl0}}{{{reporturl0}}}{{/reporturl0}}
{{#reporturl1}}{{{reporturl1}}}{{/reporturl1}}
{{#gradesonlyurl}}{{{gradesonlyurl}}}{{/gradesonlyurl}}
{{#aggregatesonlyurl}}{{{aggregatesonlyurl}}}{{/aggregatesonlyurl}}
{{#fullmodeurl}}{{{fullmodeurl}}}{{/fullmodeurl}}
{{#advancedgradingurl}}{{{advancedgradingurl}}}{{/advancedgradingurl}}
{{#deleteurl}}{{{deleteurl}}}{{/deleteurl}}
{{#duplicateurl}}{{{duplicateurl}}}{{/duplicateurl}}
{{#gradeanalysisurl}}{{{gradeanalysisurl}}}{{/gradeanalysisurl}}
{{#divider1}}
<div class="dropdown-divider" role="separator"></div>
{{/divider1}}
@ -76,6 +77,7 @@
{{/divider2}}
{{#hideurl}}{{{hideurl}}}{{/hideurl}}
{{#lockurl}}{{{lockurl}}}{{/lockurl}}
{{#resetweightsurl}}{{{resetweightsurl}}}{{/resetweightsurl}}
</div>
</div>
</div>

View file

@ -159,16 +159,20 @@ class behat_grades extends behat_base {
throw new Exception('Unknown item type: ' . $itemtype);
}
$xpath = "//table[@id='grade_edit_tree_table']";
if (($page == 'grader') || ($page == 'setup')) {
if ($page == 'grader') {
$xpath = "//table[@id='user-grades']";
}
if ($itemtype == 'gradeitem') {
$xpath = "//table[@id='user-grades']//*[@data-type='item'][@data-id='" . $itemid . "']";
$xpath .= "//*[@data-type='item'][@data-id='" . $itemid . "']";
} else if (($itemtype == 'category') || ($itemtype == 'course')) {
$xpath = "//table[@id='user-grades']//*[@data-type='category'][@data-id='" . $itemid . "']";
$xpath .= "//*[@data-type='category'][@data-id='" . $itemid . "']";
} else {
throw new Exception('Unknown item type: ' . $itemtype);
}
} else if ($page == 'setup') {
$xpath = "//table[@id='grade_edit_tree_table']//*[@data-id='" . $itemid . "']";
} else {
throw new Exception('Unknown page: ' . $page);
}

View file

@ -372,6 +372,7 @@
.weightoverride {
margin-right: 5px;
}
min-width: 10em;
}
&.column-actions {
@ -481,6 +482,14 @@
}
}
}
.badge-light {
color: #1d2125;
background-color: #ced4da;
margin-right: 0.5em;
margin-bottom: 0.5em;
}
}
}

View file

@ -34867,6 +34867,9 @@ p.arrow_button {
width: 18px;
height: 18px;
}
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr td.column-weight {
min-width: 10em;
}
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr td.column-weight .weightoverride {
margin-right: 5px;
}
@ -34942,6 +34945,12 @@ p.arrow_button {
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr.item.categoryitem td:not(.column-actions), .path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr.item.courseitem td:not(.column-actions) {
font-weight: bold;
}
.path-grade-edit-tree .gradetree-wrapper .badge-light {
color: #1d2125;
background-color: #ced4da;
margin-right: 0.5em;
margin-bottom: 0.5em;
}
/**
* Grader report.

View file

@ -34867,6 +34867,9 @@ p.arrow_button {
width: 18px;
height: 18px;
}
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr td.column-weight {
min-width: 10em;
}
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr td.column-weight .weightoverride {
margin-right: 5px;
}
@ -34942,6 +34945,12 @@ p.arrow_button {
.path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr.item.categoryitem td:not(.column-actions), .path-grade-edit-tree .gradetree-wrapper .setup-grades.generaltable tr.item.courseitem td:not(.column-actions) {
font-weight: bold;
}
.path-grade-edit-tree .gradetree-wrapper .badge-light {
color: #1d2125;
background-color: #ced4da;
margin-right: 0.5em;
margin-bottom: 0.5em;
}
/**
* Grader report.