This commit is contained in:
Andrew Nicols 2023-04-18 10:10:39 +08:00
commit df2e6c1cd4
15 changed files with 110 additions and 51 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,6 +31,7 @@ import {renderForPromise, replaceNodeContents} from 'core/templates';
const selectors = {
component: '.user-search',
courseid: '[data-region="courseid"]',
resetPageButton: '[data-action="resetpage"]',
};
const component = document.querySelector(selectors.component);
const courseID = component.querySelector(selectors.courseid).dataset.courseid;
@ -81,10 +82,10 @@ export default class UserSearch extends GradebookSearchClass {
*/
async renderDropdown() {
const {html, js} = await renderForPromise('gradereport_grader/search/resultset', {
users: this.getMatchedResults(),
users: this.getMatchedResults().slice(0, 5),
hasusers: this.getMatchedResults().length > 0,
total: this.getDatasetSize(),
found: this.getMatchedResults().length,
matches: this.getMatchedResults().length,
showing: this.getMatchedResults().slice(0, 5).length,
searchterm: this.getSearchTerm(),
selectall: this.selectAllResultsLink(),
});
@ -135,6 +136,7 @@ export default class UserSearch extends GradebookSearchClass {
this.getPreppedSearchTerm(),
`<span class="font-weight-bold">${this.getSearchTerm()}</span>`
);
user.matchingField = `${user.matchingField} (${user.email})`;
user.link = this.selectOneLink(user.id);
break;
}
@ -153,6 +155,9 @@ export default class UserSearch extends GradebookSearchClass {
if (e.target === this.getHTMLElements().currentViewAll && e.button === 0) {
window.location = this.selectAllResultsLink();
}
if (e.target.closest(selectors.resetPageButton)) {
window.location = e.target.closest(selectors.resetPageButton).href;
}
}
/**
@ -180,11 +185,18 @@ export default class UserSearch extends GradebookSearchClass {
}
}
if (document.activeElement === this.getHTMLElements().clearSearchButton) {
this.closeSearch();
this.closeSearch(true);
break;
}
if (e.target.closest(selectors.resetPageButton)) {
window.location = e.target.closest(selectors.resetPageButton).href;
break;
}
if (e.target.closest('.dropdown-item')) {
e.preventDefault();
window.location = e.target.closest('.dropdown-item').href;
break;
}
e.preventDefault();
window.location = e.target.closest('.dropdown-item').href;
break;
}
}

View File

@ -222,14 +222,18 @@ export default class {
/**
* When called, close the dropdown and reset the input field attributes.
*
* @param {Boolean} clear Conditionality clear the input box.
*/
closeSearch() {
closeSearch(clear = false) {
this.toggleDropdown();
// Hide the "clear" search button search bar.
this.clearSearchButton.classList.add('d-none');
// Clear the entered search query in the search bar and hide the search results container.
this.setSearchTerms('');
this.searchInput.value = "";
if (clear) {
// Clear the entered search query in the search bar and hide the search results container.
this.setSearchTerms('');
this.searchInput.value = "";
}
}
/**
@ -379,7 +383,7 @@ export default class {
}
// The "clear search" button is triggered.
if (e.target.closest(this.selectors.clearSearch) && e.button === 0) {
this.closeSearch();
this.closeSearch(true);
this.searchInput.focus({preventScroll: true});
}
// User may have accidentally clicked off the dropdown and wants to reopen it.
@ -418,7 +422,7 @@ export default class {
case 'Tab':
// If the current focus is on clear search, then check if viewall exists then around tab to it.
if (e.target.closest(this.selectors.clearSearch)) {
if (this.currentViewAll) {
if (this.currentViewAll && !e.shiftKey) {
e.preventDefault();
this.currentViewAll.focus({preventScroll: true});
} else {

View File

@ -101,7 +101,7 @@ class get_users_in_report extends external_api {
$userpicture->size = 0; // Size f2.
$user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
return $user;
}, $report->load_users());
}, $report->load_users(true));
sort($users);
return [

View File

@ -92,9 +92,11 @@ class action_bar extends \core_grades\output\action_bar {
$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('gradereport_grader/search/searchinput', [
'currentvalue' => $this->usersearch,
'courseid' => $courseid,
'resetlink' => $resetlink->out(false),
]);
$searchdropdown = new gradebook_dropdown(
true,

View File

@ -23,6 +23,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['clearsearch'] = 'Clear searched users';
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge badge-pill badge-primary ml-1" data-collapse="count">{$a}</span>';
$string['eventgradereportviewed'] = 'Grader report viewed';
$string['grader:manage'] = 'Manage the grader report';
@ -46,12 +47,11 @@ $string['privacy:metadata:preference:grade_report_studentsperpage'] = 'The numbe
$string['privacy:request:preference:grade_report_grader_collapsed_categories'] = 'You have some gradebook categories collapsed in the "{$a->name}" course';
$string['reopencolumn'] = 'Reopen {$a} column';
$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['showingxofy'] = 'Showing {$a->found} of {$a->total}';
$string['useractivitygrade'] = '{$a} grade';
$string['overriddengrade'] = 'Overridden grade';
$string['advancedgrading'] = 'View {$a} results';
$string['cellactions'] = 'Cell actions';
$string['viewallresults'] = 'View all results for "{$a}"';
$string['viewallresults'] = 'View all results ({$a})';
$string['viewresultsuser'] = 'View results for {$a}';
// Deprecated since Moodle 4.2.

View File

@ -389,8 +389,10 @@ class grade_report_grader extends grade_report {
/**
* pulls out the userids of the users to be display, and sorts them
*
* @param bool $allusers If we are getting the users within the report, we want them all irrespective of paging.
*/
public function load_users() {
public function load_users(bool $allusers = false) {
global $CFG, $DB;
if (!empty($this->users)) {
@ -465,7 +467,8 @@ class grade_report_grader extends grade_report {
$this->groupwheresql
ORDER BY $sort";
// We never work with unlimited result. Limit the number of records by MAX_STUDENTS_PER_PAGE if no other limit is specified.
$studentsperpage = $this->get_students_per_page() ?: static::MAX_STUDENTS_PER_PAGE;
$studentsperpage = ($this->get_students_per_page() && !$allusers) ?
$this->get_students_per_page() : static::MAX_STUDENTS_PER_PAGE;
$this->users = $DB->get_records_sql($sql, $params, $studentsperpage * $this->page, $studentsperpage);
if (empty($this->users)) {

View File

@ -234,7 +234,8 @@
pointer-events: initial;
z-index: 1;
}
.path-grade-report-grader .usersearchwidget button {
.path-grade-report-grader .usersearchwidget button,
.path-grade-report-grader .usersearchwidget a {
pointer-events: initial;
}
.path-grade-report-grader .usersearchdropdown {

View File

@ -45,10 +45,10 @@
{{/profileimageurl}}
</div>
<div class="d-block pr-3 w-75">
<span class="d-flex w-100 p-0 font-weight-bold">
<span class="d-block w-100 p-0 text-truncate font-weight-bold">
{{fullname}}
</span>
<span class="d-flex w-100 pull-left text-truncate small" aria-hidden="true">
<span class="d-block w-100 pull-left text-truncate small" aria-hidden="true">
{{{matchingField}}}
</span>
<span class="sr-only" aria-label="{{#str}}usermatchedon, core_grades{{/str}}">{{matchingFieldName}}</span>

View File

@ -44,8 +44,7 @@
"link": "http://foo.bar/grade/report/grader/index.php?id=42&userid=3"
}
],
"found": 2,
"total": 20,
"matches": 20,
"selectall": "https://foo.bar/grade/report/grader/index.php?id=2&searchvalue=abe",
"searchterm": "Foo",
"hasusers": true
@ -53,17 +52,16 @@
}}
<div class="d-flex flex-column mh-100 h-100">
{{#hasusers}}
<span class="small d-block px-4 my-2">{{#str}}showingxofy, gradereport_grader, {"found": "{{found}}", "total": "{{total}}"}{{/str}}</span>
<ul class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group h-100 mh-100 mx-0" role="listbox" data-region="search-result-items-container" tabindex="-1">
{{#users}}
{{>gradereport_grader/search/resultitem}}
{{/users}}
<li class="w-100 result-row p-1 border-top bottom-0 position-sticky" role="none" id="result-row-{{id}}">
<a role="option" class="dropdown-item d-flex small p-3" id="select-all" href="{{{selectall}}}" tabindex="-1">
{{#str}}viewallresults, gradereport_grader, {{matches}}{{/str}}
</a>
</li>
</ul>
<div class="unsearchablecontentcontainer p-1">
<a role="button" id="select-all" href="{{{selectall}}}" class="small d-block p-4" tabindex="-1">
{{#str}}viewallresults, gradereport_grader, {{searchterm}}{{/str}}
</a>
</div>
{{/hasusers}}
{{^hasusers}}
<span class="small d-block px-4 my-4">{{#str}} noresultsfor, core_grades, {{searchterm}}{{/str}}</span>

View File

@ -23,7 +23,8 @@
Example context (json):
{
"currentvalue": "bar",
"courseid": 2
"courseid": 2,
"resetlink": "grade/report/grader/index.php?id=2"
}
}}
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}" aria-hidden="true"></span>
@ -34,6 +35,9 @@
{{/str}}{{/label}}
{{$value}}{{{currentvalue}}}{{/value}}
{{/ core/search_input_auto }}
<a class="ml-2 btn btn-link border-0" href="{{resetlink}}" data-action="resetpage" role="link" aria-label="{{#str}}clearsearch, gradereport_grader{{/str}}">
{{#str}}clear{{/str}}
</a>
{{/currentvalue}}
{{^currentvalue}}
{{< core/search_input_auto }}

View File

@ -49,7 +49,7 @@ Feature: Within the grader report, test that we can search for users
| -1- |
| Teacher 1 |
When I set the field "Search users" to "Turtle"
And I wait until "View all results for \"Turtle\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And "Turtle Manatee" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should not exist in the ".user-search" "css_element"
And I click on "Turtle Manatee" "list_item"
@ -66,14 +66,14 @@ Feature: Within the grader report, test that we can search for users
| User Test |
| Dummy User |
And I set the field "Search users" to "Turt"
And I wait until "View all results for \"Turt\"" "button" exists
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 for \"Turt\"" "button" should not be visible
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 set the field "Search users" to "Dummy"
And I wait until "View all results for \"Dummy\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And I click on "Dummy User" "option_role"
And I wait until the page is ready
And the following should exist in the "user-grades" table:
@ -104,11 +104,15 @@ Feature: Within the grader report, test that we can search for users
# 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 for \"User\"" "button" exists
And I wait until "View all results (3)" "option_role" exists
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should exist in the ".user-search" "css_element"
And "User Test" "list_item" should exist in the ".user-search" "css_element"
And "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
And "User (student2@example.com)" "list_item" should exist in the ".user-search" "css_element"
And "User (student3@example.com)" "list_item" should exist in the ".user-search" "css_element"
And "User (student4@example.com)" "list_item" should exist in the ".user-search" "css_element"
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:
@ -125,12 +129,11 @@ Feature: Within the grader report, test that we can search for users
# 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.
# TODO: Need to look at maybe adding the users email or something in the case multiple matches exist?
And I set the field "Search users" to "User"
And I wait until "View all results for \"User\"" "button" exists
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 "View all results for \"User\"" "button" should exist
And I click on "View all results for \"User\"" "button"
And "View all results (3)" "option_role" should exist
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- |
@ -142,15 +145,24 @@ Feature: Within the grader report, test that we can search for users
| 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
Given I set the field "Search users" to "Turtle"
And I wait until "View all results for \"Turtle\"" "button" exists
And I wait until "View all results (1)" "option_role" exists
And I click on "Turtle Manatee" "list_item"
And I wait until the page is ready
# The search input remains in the field on reload this is in keeping with other search implementations.
When the field "Search users" matches value "Turtle"
And "View all results for \"Turtle\"" "link" should not exist
And "View all results (1)" "link" should not exist
# Test if we can then further retain the turtle result set and further filter from there.
Then I set the field "Search users" to "Turtle plagiarism"
And "Turtle Manatee" "list_item" should not exist
@ -158,10 +170,10 @@ Feature: Within the grader report, test that we can search for users
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 for \"student5@example.com\"" "button" exists
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 for \"@example.com\"" "button" exists
And I wait until "View all results (5)" "option_role" exists
# Note: All learners match this email & showing emails is current default.
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
And "User Example" "list_item" should exist in the ".user-search" "css_element"
@ -238,11 +250,11 @@ Feature: Within the grader report, test that we can search for users
And I press the down key
And the focused element is "Student 1" "option_role"
And I press the end key
And the focused element is "Turtle Manatee" "option_role"
And the focused element is "View all results (5)" "option_role"
And I press the home key
And the focused element is "Student 1" "option_role"
And I press the up key
And the focused element is "Turtle Manatee" "option_role"
And the focused element is "View all results (5)" "option_role"
And I press the down key
And the focused element is "Student 1" "option_role"
And I press the escape key
@ -250,14 +262,20 @@ Feature: Within the grader report, test that we can search for users
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 the focused element is "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And I wait "3" seconds
And I wait until "View all results for \"ABC\"" "button" exists
And I wait until "View all results (5)" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the tab key
And the focused element is "View all results for \"ABC\"" "button"
And the focused element is "View all results (5)" "option_role"
And I press the tab key
And the focused element is ".search-widget[data-searchtype='group'] [data-toggle='dropdown']" "css_element"
# Ensure we can interact with the input & clear search options with the keyboard.
@ -307,3 +325,20 @@ Feature: Within the grader report, test that we can search for users
# Begin the search checking if we are adhering the filters.
When I set the field "Search users" to "Turtle"
Then "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
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)" "link" exists
Then "Student s42" "list_item" should exist in the ".user-search" "css_element"