Skip to content

Commit

Permalink
Add student filter to student progress page and update stats filter.
Browse files Browse the repository at this point in the history
  This adds the ability to filter by section or recitation when
  viewing the student progress of a single set. This also updates
  the link parameters to ensure that any current filter, test display
  options, or sort is saved when updating the page view.

  This also updates the Stats.pm filter to use the Utils::filterRecords
  method instead of its own method.
  • Loading branch information
somiaj committed Feb 15, 2024
1 parent 3b958c2 commit f2bf400
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 64 deletions.
44 changes: 13 additions & 31 deletions lib/WeBWorK/ContentGenerator/Instructor/Stats.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ homework set (including sv graphs).
use SVG;

use WeBWorK::Utils qw(jitar_id_to_seq jitar_problem_adjusted_status format_set_name_display grade_set);
use WeBWorK::Utils::FilterRecords qw(getFiltersForClass filterRecords);

sub initialize ($c) {
my $db = $c->db;
Expand All @@ -38,9 +39,9 @@ sub initialize ($c) {
# Cache a list of all users except set level proctors and practice users, and restrict to the sections or
# recitations that are allowed for the user if such restrictions are defined. This list is sorted by last_name,
# then first_name, then user_id. This is used in multiple places in this module, and is guaranteed to be used at
# least once. So it is done here to prevent extra database access.
# least once. So it is done here to prevent extra database access. Filter out users not included in stats.
$c->{student_records} = [
$db->getUsersWhere(
grep { $ce->status_abbrev_has_behavior($_->status, 'include_in_stats') } $db->getUsersWhere(
{
user_id => [ -and => { not_like => 'set_id:%' }, { not_like => "$ce->{practiceUserPrefix}\%" } ],
$ce->{viewable_sections}{$user} || $ce->{viewable_recitations}{$user}
Expand Down Expand Up @@ -100,39 +101,18 @@ sub siblings ($c) {
return $c->include('ContentGenerator/Instructor/Stats/siblings', header => $c->maketext('Statistics'));
}

# Apply the currently selected filter to the the student records cached in initialize, and return a reference to the
# Apply the currently selected filter to the student records, and return a reference to the
# list of students and a reference to a hash of section/recitation filters.
sub filter_students ($c) {
my $ce = $c->ce;
my $db = $c->db;
my $user = $c->param('user');
my $filter = $c->param('filter');
my @students = $filter ? filterRecords($c, 0, [$filter], @{ $c->{student_records} }) : @{ $c->{student_records} };

# Create a hash of sections and recitations, if there are any for the course.
# Filter out all records except for current/auditing students for stats.
# Filter out students not in selected section/recitation.
my $filter = $c->param('filter');
my %filters;
my @outStudents;
for my $student (@{ $c->{student_records} }) {
# Only include current/auditing students in stats.
next
unless ($ce->status_abbrev_has_behavior($student->status, 'include_in_stats'));

my $section = $student->section;
$filters{"section:$section"} = $c->maketext('Section [_1]', $section)
if $section && !$filters{"section:$section"};
my $recitation = $student->recitation;
$filters{"recitation:$recitation"} = $c->maketext('Recitation [_1]', $recitation)
if $recitation && !$filters{"recitation:$recitation"};

# Only add users who match the selected section/recitation.
push(@outStudents, $student)
if (!$filter
|| ($filter =~ /^section:(.*)$/ && $section eq $1)
|| ($filter =~ /^recitation:(.*)$/ && $recitation eq $1));
}
# convert the array from getFiltersForClass to a hash, after removing the first 'all' filter.
my $filters = getFiltersForClass($c, [ 'section', 'recitation' ], @{ $c->{student_records} });
shift(@$filters);
$filters = { map { $_->[1] => $_->[0] } @$filters };

return (\@outStudents, \%filters);
return (\@students, $filters);
}

sub set_stats ($c) {
Expand Down Expand Up @@ -190,6 +170,7 @@ sub set_stats ($c) {
# Only count top level problems for Jitar sets.
my $num_problems = $isJitarSet ? scalar(keys %topLevelProblems) : scalar(@problems);

my $filter = $c->param('filter');
my ($students, $filters) = $c->filter_students;
for my $studentRecord (@$students) {
my $student = $studentRecord->user_id;
Expand Down Expand Up @@ -381,6 +362,7 @@ sub problem_stats ($c) {
my $db = $c->db;
my $ce = $c->ce;
my $user = $c->param('user');
my $filter = $c->param('filter');
my $courseID = $c->stash('courseID');
my $problemID = $c->stash('problemID');

Expand Down
20 changes: 16 additions & 4 deletions lib/WeBWorK/ContentGenerator/Instructor/StudentProgress.pm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ WeBWorK::ContentGenerator::Instructor::StudentProgress - Display Student Progres

use WeBWorK::Utils qw(jitar_id_to_seq wwRound grade_set format_set_name_display);
use WeBWorK::Utils::Grades qw(list_set_versions);
use WeBWorK::Utils::FilterRecords qw(getFiltersForClass filterRecords);

sub initialize ($c) {
my $db = $c->db;
Expand All @@ -36,9 +37,9 @@ sub initialize ($c) {
# Cache a list of all users except set level proctors and practice users, and restrict to the sections or
# recitations that are allowed for the user if such restrictions are defined. This list is sorted by last_name,
# then first_name, then user_id. This is used in multiple places in this module, and is guaranteed to be used at
# least once. So it is done here to prevent extra database access.
# least once. So it is done here to prevent extra database access. Filter out users not included in stats.
$c->{student_records} = [
$db->getUsersWhere(
grep { $ce->status_abbrev_has_behavior($_->status, 'include_in_stats') } $db->getUsersWhere(
{
user_id => [ -and => { not_like => 'set_id:%' }, { not_like => "$ce->{practiceUserPrefix}\%" } ],
$ce->{viewable_sections}{$user} || $ce->{viewable_recitations}{$user}
Expand Down Expand Up @@ -115,10 +116,19 @@ sub displaySets ($c) {
: (date => 0, testtime => 0, timeleft => 0, problems => 1, section => 1, recit => 1, login => 1);
my $showBestOnly = $setIsVersioned ? $c->param('show_best_only') : 0;

my $filter = $c->param('filter');
my @student_records =
$filter ? filterRecords($c, 0, [$filter], @{ $c->{student_records} }) : @{ $c->{student_records} };

# convert the array from getFiltersForClass to a hash, after removing the first 'all' filter.
my $filters = getFiltersForClass($c, [ 'section', 'recitation' ], @{ $c->{student_records} });
shift(@$filters);
$filters = { map { $_->[1] => $_->[0] } @$filters };

my @score_list;
my @user_set_list;

for my $studentRecord (@{ $c->{student_records} }) {
for my $studentRecord (@student_records) {
next unless $ce->status_abbrev_has_behavior($studentRecord->status, 'include_in_stats');

my $studentName = $studentRecord->user_id;
Expand Down Expand Up @@ -277,7 +287,9 @@ sub displaySets ($c) {
secondary_sort_method => $secondary_sort_method,
ternary_sort_method => $ternary_sort_method,
problems => \@problems,
user_set_list => \@user_set_list
user_set_list => \@user_set_list,
filters => $filters,
filter => $filter,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
%
<div class="mb-3">
<%= maketext('Showing statistics for:') =%>
<%= include 'ContentGenerator/Instructor/Stats/student_filter_menu', filters => $filters =%>
<%= include 'HTML/StudentFilterMenu/menu', filters => $filters, params => {} =%>
<%= include 'ContentGenerator/Instructor/Stats/problem_menu', problems => $problems =%>
</div>
%
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
% # Filter and problem selectors.
<div class="d-flex align-items-center mt-1 mb-3">
<%= maketext('Showing statistics for:') =%>
<%= include 'ContentGenerator/Instructor/Stats/student_filter_menu', filters => $filters =%>
<%= include 'HTML/StudentFilterMenu/menu', filters => $filters, params => {} =%>
<%= include 'ContentGenerator/Instructor/Stats/problem_menu', problems => $problems =%>
</div>
% # Set information
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
<%= include 'ContentGenerator/Base/set_status', set => $c->{setRecord} =%>
%
% my %params = (
% defined $primary_sort_method ? (primary_sort => $primary_sort_method) : (),
% defined $secondary_sort_method ? (secondary_sort => $secondary_sort_method) : (),
% defined $ternary_sort_method ? (ternary_sort => $ternary_sort_method) : (),
% defined $filter ? (filter => $filter) : (),
% # Preserve display options when the sort headers are clicked for gateway quizzes.
% $setIsVersioned
% ? (
% show_best_only => $showBestOnly,
% show_date => $showColumns->{date},
% show_testtime => $showColumns->{testtime},
% show_timeleft => $showColumns->{timeleft},
% show_problems => $showColumns->{problems},
% show_section => $showColumns->{section},
% show_recitation => $showColumns->{recit},
% show_login => $showColumns->{login},
% )
% : ()
% );
%
% # Filter selector.
% if (%$filters) {
<div class="d-flex align-items-center mt-1 mb-3">
<%= maketext('Showing progress for:') =%>
<%= include 'HTML/StudentFilterMenu/menu', filters => $filters, params => \%params =%>
</div>
% }
%
% # In the case of gateway tests, add a form with checkboxes that allow customization of what is included in the
% # display.
% if ($setIsVersioned) {
Expand Down Expand Up @@ -60,6 +88,18 @@
</label>
</div>
</div>
% if (param('filter')) {
<%= hidden_field filter => param('filter') =%>
% }
% if ($primary_sort_method) {
<%= hidden_field primary_sort => $primary_sort_method =%>
% }
% if ($secondary_sort_method) {
<%= hidden_field secondary_sort => $secondary_sort_method =%>
% }
% if ($ternary_sort_method) {
<%= hidden_field ternary_sort => $ternary_sort_method =%>
% }
<%= submit_button maketext('Update Display'), class => 'btn btn-primary' =%>
</div>
<% end =%>
Expand Down Expand Up @@ -106,23 +146,11 @@
% }
</div>
%
% my %params = (
% my %sort_params = (
% %params,
% # Shift past sort methods down in priority.
% defined $primary_sort_method ? (secondary_sort => $primary_sort_method) : (),
% defined $secondary_sort_method ? (ternary_sort => $secondary_sort_method) : (),
% # Preserve display options when the sort headers are clicked for gateway quizzes.
% $setIsVersioned
% ? (
% show_best_only => $showBestOnly,
% show_date => $showColumns->{date},
% show_testtime => $showColumns->{testtime},
% show_timeleft => $showColumns->{timeleft},
% show_problems => $showColumns->{problems},
% show_section => $showColumns->{section},
% show_recitation => $showColumns->{recit},
% show_login => $showColumns->{login},
% )
% : ()
% defined $primary_sort_method ? (secondary_sort => $primary_sort_method) : (),
% defined $secondary_sort_method ? (ternary_sort => $secondary_sort_method) : (),
% );
%
% # Start table output
Expand All @@ -135,17 +163,17 @@
<%= maketext('Name') %>
<br>
<%= link_to maketext('First') =>
$c->systemLink(url_for, params => { primary_sort => 'first_name', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'first_name' }) =%>
&nbsp;&nbsp;&nbsp;
<%= link_to maketext('Last') =>
$c->systemLink(url_for, params => { primary_sort => 'last_name', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'last_name' }) =%>
<br>
<%= link_to maketext('Email') =>
$c->systemLink(url_for, params => { primary_sort => 'email_address', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'email_address' }) =%>
</th>
<th <%== $rowspan %>>
<%= link_to maketext('Score') =>
$c->systemLink(url_for, params => { primary_sort => 'score', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'score' }) =%>
</th>
<th <%== $rowspan %>><%= maketext('Out Of') %></th>
% # Additional columns that may be shown depending on if showing a gateway quiz and user selection.
Expand Down Expand Up @@ -176,19 +204,19 @@
% if ($showColumns->{section}) {
<th <%== $rowspan %>>
<%= link_to maketext('Section') =>
$c->systemLink(url_for, params => { primary_sort => 'section', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'section' }) =%>
</th>
% }
% if ($showColumns->{recit}) {
<th <%== $rowspan %>>
<%= link_to maketext('Recitation') =>
$c->systemLink(url_for, params => { primary_sort => 'recitation', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'recitation' }) =%>
</th>
% }
% if ($showColumns->{login}) {
<th <%== $rowspan %>>
<%= link_to maketext('Login Name') =>
$c->systemLink(url_for, params => { primary_sort => 'user_id', %params }) =%>
$c->systemLink(url_for, params => { %sort_params, primary_sort => 'user_id' }) =%>
</th>
% }
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
%
% # Create a section/recitation "filter by" dropdown if there are sections or recitations.
<div class="btn-group student-nav-filter-selector mx-2">
<%= link_to param('filter') ? $filters->{param('filter')} : maketext('All sections') => '#',
<%= link_to param('filter') ? $filters->{param('filter')} : maketext('All students') => '#',
id => 'filter', class => 'btn btn-primary dropdown-toggle', role => 'button', 'aria-expanded' => 'false',
data => { bs_toggle => 'dropdown' } =%>
<ul class="dropdown-menu" role="menu" aria-labelledby="filter">
<li>
<%= link_to maketext('All sections') => $c->systemLink(url_for),
<%= link_to maketext('All students') => $c->systemLink(url_for),
class => 'dropdown-item', param('filter') ? () : (style => 'background-color:#8F8') =%>
</li>
% for (sort keys %$filters) {
<li>
<%= link_to $filters->{$_} => $c->systemLink(url_for, params => { filter => $_ }),
<%= link_to $filters->{$_} => $c->systemLink(url_for, params => { %$params, filter => $_ }),
(param('filter') || '') eq $_ ? (style => 'background-color: #8F8') : (),
class => 'dropdown-item' =%>
</li>
% }
</ul>
</div>\
</div>

0 comments on commit f2bf400

Please sign in to comment.