From 44116b43e36870e55fa698914280da3e77c097a0 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sat, 21 Oct 2023 10:08:25 -0500 Subject: [PATCH] Add reverse sorting. --- htdocs/js/JobManager/jobmanager.js | 15 ++- .../ContentGenerator/Instructor/JobManager.pm | 72 ++++++++--- .../Instructor/JobManager.html.ep | 71 ++++++++--- .../Instructor/JobManager/sort_form.html.ep | 117 +++++++++++++----- 4 files changed, 202 insertions(+), 73 deletions(-) diff --git a/htdocs/js/JobManager/jobmanager.js b/htdocs/js/JobManager/jobmanager.js index c64344835c..b72cfe12e3 100644 --- a/htdocs/js/JobManager/jobmanager.js +++ b/htdocs/js/JobManager/jobmanager.js @@ -11,7 +11,7 @@ toggle_filter_elements(); } - // Submit the job list form when a sort header is clicked or enter or space is pressed when it has focus. + // Submit the form when a sort header is clicked or enter or space is pressed when it has focus. const currentAction = document.getElementById('current_action'); if (currentAction) { for (const header of document.querySelectorAll('.sort-header')) { @@ -33,6 +33,19 @@ header.addEventListener('keydown', (e) => { if (e.key === ' ' || e.key === 'Enter') submitSortMethod(e); }); + + const orderToggleButton = header.parentElement.querySelector('button.sort-order'); + orderToggleButton?.addEventListener('click', () => { + currentAction.value = 'sort'; + + const sortOrderInput = document.createElement('input'); + sortOrderInput.name = 'labelSortOrder'; + sortOrderInput.value = orderToggleButton.dataset.sortPriority; + sortOrderInput.type = 'hidden'; + currentAction.form.append(sortOrderInput); + + currentAction.form.submit(); + }); } } diff --git a/lib/WeBWorK/ContentGenerator/Instructor/JobManager.pm b/lib/WeBWorK/ContentGenerator/Instructor/JobManager.pm index cb8190c5c5..d43a9a3302 100644 --- a/lib/WeBWorK/ContentGenerator/Instructor/JobManager.pm +++ b/lib/WeBWorK/ContentGenerator/Instructor/JobManager.pm @@ -46,13 +46,13 @@ use constant FIELDS => [ ]; use constant SORT_SUBS => { - id => \&byJobID, - courseID => \&byCourseID, - task => \&byTask, - created => \&byCreatedTime, - started => \&byStartedTime, - finished => \&byFinishedTime, - state => \&byState + id => { ASC => \&byJobID, DESC => \&byDescJobID }, + courseID => { ASC => \&byCourseID, DESC => \&byDescCourseID }, + task => { ASC => \&byTask, DESC => \&byDescTask }, + created => { ASC => \&byCreatedTime, DESC => \&byDescCreatedTime }, + started => { ASC => \&byStartedTime, DESC => \&byDescStartedTime }, + finished => { ASC => \&byFinishedTime, DESC => \&byDescFinishedTime }, + state => { ASC => \&byState, DESC => \&byDescState } }; sub initialize ($c) { @@ -64,8 +64,11 @@ sub initialize ($c) { $c->stash->{selectedJobs} = {}; $c->stash->{sortedJobs} = []; $c->stash->{primarySortField} = $c->param('primarySortField') || 'created'; + $c->stash->{primarySortOrder} = $c->param('primarySortOrder') || 'ASC'; $c->stash->{secondarySortField} = $c->param('secondarySortField') || 'task'; + $c->stash->{secondarySortOrder} = $c->param('secondarySortOrder') || 'ASC'; $c->stash->{ternarySortField} = $c->param('ternarySortField') || 'state'; + $c->stash->{ternarySortOrder} = $c->param('ternarySortOrder') || 'ASC'; return unless $c->authz->hasPermissions($c->param('user'), 'access_instructor_tools'); @@ -108,9 +111,9 @@ sub initialize ($c) { } # Sort jobs - my $primarySortSub = SORT_SUBS()->{ $c->stash->{primarySortField} }; - my $secondarySortSub = SORT_SUBS()->{ $c->stash->{secondarySortField} }; - my $ternarySortSub = SORT_SUBS()->{ $c->stash->{ternarySortField} }; + my $primarySortSub = SORT_SUBS()->{ $c->stash->{primarySortField} }{ $c->stash->{primarySortOrder} }; + my $secondarySortSub = SORT_SUBS()->{ $c->stash->{secondarySortField} }{ $c->stash->{secondarySortOrder} }; + my $ternarySortSub = SORT_SUBS()->{ $c->stash->{ternarySortField} }{ $c->stash->{ternarySortOrder} }; # byJobID is included to ensure a definite sort order in case the # first three sorts do not determine a proper order. @@ -148,26 +151,47 @@ sub filter_handler ($c) { } sub sort_handler ($c) { - if (defined $c->param('labelSortMethod')) { - $c->stash->{ternarySortField} = $c->stash->{secondarySortField}; - $c->stash->{secondarySortField} = $c->stash->{primarySortField}; - $c->stash->{primarySortField} = $c->param('labelSortMethod'); - $c->param('action.sort.primary', $c->stash->{primarySortField}); - $c->param('action.sort.secondary', $c->stash->{secondarySortField}); - $c->param('action.sort.ternary', $c->stash->{ternarySortField}); + if (defined $c->param('labelSortMethod') || defined $c->param('labelSortOrder')) { + if (defined $c->param('labelSortOrder')) { + $c->stash->{ $c->param('labelSortOrder') . 'SortOrder' } = + $c->stash->{ $c->param('labelSortOrder') . 'SortOrder' } eq 'ASC' ? 'DESC' : 'ASC'; + } elsif ($c->param('labelSortMethod') eq $c->stash->{primarySortField}) { + $c->stash->{primarySortOrder} = $c->stash->{primarySortOrder} eq 'ASC' ? 'DESC' : 'ASC'; + } else { + $c->stash->{ternarySortField} = $c->stash->{secondarySortField}; + $c->stash->{ternarySortOrder} = $c->stash->{secondarySortOrder}; + $c->stash->{secondarySortField} = $c->stash->{primarySortField}; + $c->stash->{secondarySortOrder} = $c->stash->{primarySortOrder}; + $c->stash->{primarySortField} = $c->param('labelSortMethod'); + $c->stash->{primarySortOrder} = 'ASC'; + } + + $c->param('action.sort.primary', $c->stash->{primarySortField}); + $c->param('action.sort.primary.order', $c->stash->{primarySortOrder}); + $c->param('action.sort.secondary', $c->stash->{secondarySortField}); + $c->param('action.sort.secondary.order', $c->stash->{secondarySortOrder}); + $c->param('action.sort.ternary', $c->stash->{ternarySortField}); + $c->param('action.sort.ternary.order', $c->stash->{ternarySortOrder}); } else { $c->stash->{primarySortField} = $c->param('action.sort.primary'); + $c->stash->{primarySortOrder} = $c->param('action.sort.primary.order'); $c->stash->{secondarySortField} = $c->param('action.sort.secondary'); + $c->stash->{secondarySortOrder} = $c->param('action.sort.secondary.order'); $c->stash->{ternarySortField} = $c->param('action.sort.ternary'); + $c->stash->{ternarySortOrder} = $c->param('action.sort.ternary.order'); } return $c->maketext( - 'Users sorted by [_1], then by [_2], then by [_3]', + 'Sets sorted by [_1] in [plural,_2,ascending,descending] order, ' + . 'then by [_3] in [plural,_4,ascending,descending] order,' + . 'and then by [_5] in [plural,_6,ascending,descending] order.', $c->maketext((grep { $_->[0] eq $c->stash->{primarySortField} } @{ FIELDS() })[0][1]), + $c->stash->{primarySortOrder} eq 'ASC' ? 1 : 2, $c->maketext((grep { $_->[0] eq $c->stash->{secondarySortField} } @{ FIELDS() })[0][1]), - $c->maketext((grep { $_->[0] eq $c->stash->{ternarySortField} } @{ FIELDS() })[0][1]) + $c->stash->{secondarySortOrder} eq 'ASC' ? 1 : 2, + $c->maketext((grep { $_->[0] eq $c->stash->{ternarySortField} } @{ FIELDS() })[0][1]), + $c->stash->{ternarySortOrder} eq 'ASC' ? 1 : 2 ); - } sub delete_handler ($c) { @@ -202,4 +226,12 @@ sub byStartedTime { return ($a->{started} || 0) <=> ($b->{started} || 0) } sub byFinishedTime { return ($a->{finished} || 0) <=> ($b->{finished} || 0) } sub byState { return $a->{state} cmp $b->{state} } +sub byDescJobID { local ($b, $a) = ($a, $b); return byJobID(); } +sub byDescCourseID { local ($b, $a) = ($a, $b); return byCourseID(); } +sub byDescTask { local ($b, $a) = ($a, $b); return byTask(); } +sub byDescCreatedTime { local ($b, $a) = ($a, $b); return byCreatedTime(); } +sub byDescStartedTime { local ($b, $a) = ($a, $b); return byStartedTime(); } +sub byDescFinishedTime { local ($b, $a) = ($a, $b); return byFinishedTime(); } +sub byDescState { local ($b, $a) = ($a, $b); return byState(); } + 1; diff --git a/templates/ContentGenerator/Instructor/JobManager.html.ep b/templates/ContentGenerator/Instructor/JobManager.html.ep index 27a68ec442..0cdb8e2f9b 100644 --- a/templates/ContentGenerator/Instructor/JobManager.html.ep +++ b/templates/ContentGenerator/Instructor/JobManager.html.ep @@ -32,8 +32,11 @@ % } % <%= hidden_field primarySortField => $primarySortField =%> + <%= hidden_field primarySortOrder => $primarySortOrder =%> <%= hidden_field secondarySortField => $secondarySortField =%> + <%= hidden_field secondarySortOrder => $secondarySortOrder =%> <%= hidden_field ternarySortField => $ternarySortField =%> + <%= hidden_field ternarySortOrder => $ternarySortOrder =%> % % # Output action forms % for my $form (@$actionForms) { @@ -74,41 +77,71 @@ - % if ($courseID eq 'admin') { % } diff --git a/templates/ContentGenerator/Instructor/JobManager/sort_form.html.ep b/templates/ContentGenerator/Instructor/JobManager/sort_form.html.ep index 8f21fc6df5..5505d718d4 100644 --- a/templates/ContentGenerator/Instructor/JobManager/sort_form.html.ep +++ b/templates/ContentGenerator/Instructor/JobManager/sort_form.html.ep @@ -1,41 +1,92 @@
-
- <%= label_for sort_select_1 => maketext('Sort by') . ':', class => 'col-form-label col-form-label-sm', - style => 'width:4.5rem' =%> -
- <%= select_field 'action.sort.primary' => [ - map { [ - maketext($_->[1]) => $_->[0], - $_->[0] eq 'created' ? (selected => undef) : () - ] } @$fields - ], - id => 'sort_select_1', class => 'form-select form-select-sm' =%> +
+
+
+ <%= label_for sort_select_1 => maketext('Sort by') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.primary' => [ + map { [ + maketext($_->[1]) => $_->[0], + $_->[0] eq 'created' ? (selected => undef) : () + ] } @$fields + ], + id => 'sort_select_1', class => 'form-select form-select-sm' =%> +
+
+
+
+
+ <%= label_for sort_order_select_1 => maketext('Ordered') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.primary.order' => [ + [ maketext('Ascending') => 'ASC' ], + [ maketext('Descending') => 'DESC' ], + ], + id => 'sort_order_select_1', class => 'form-select form-select-sm' =%> +
+
-
- <%= label_for sort_select_2 => maketext('Then by') . ':', class => 'col-form-label col-form-label-sm', - style => 'width:4.5rem' =%> -
- <%= select_field 'action.sort.secondary' => [ - map { [ - maketext($_->[1]) => $_->[0], - $_->[0] eq 'task' ? (selected => undef) : () - ] } @$fields - ], - id => 'sort_select_2', class => 'form-select form-select-sm' =%> +
+
+
+ <%= label_for sort_select_2 => maketext('Then by') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.secondary' => [ + map { [ + maketext($_->[1]) => $_->[0], + $_->[0] eq 'task' ? (selected => undef) : () + ] } @$fields + ], + id => 'sort_select_2', class => 'form-select form-select-sm' =%> +
+
+
+
+
+ <%= label_for sort_order_select_2 => maketext('Ordered') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.secondary.order' => [ + [ maketext('Ascending') => 'ASC' ], + [ maketext('Descending') => 'DESC' ], + ], + id => 'sort_order_select_2', class => 'form-select form-select-sm' =%> +
+
-
- <%= label_for sort_select_3 => maketext('Then by') . ':', class => 'col-form-label col-form-label-sm', - style => 'width:4.5rem' =%> -
- <%= select_field 'action.sort.ternary' => [ - map { [ - maketext($_->[1]) => $_->[0], - $_->[0] eq 'state' ? (selected => undef) : () - ] } @$fields - ], - id => 'sort_select_3', class => 'form-select form-select-sm' =%> +
+
+
+ <%= label_for sort_select_3 => maketext('Then by') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.ternary' => [ + map { [ + maketext($_->[1]) => $_->[0], + $_->[0] eq 'state' ? (selected => undef) : () + ] } @$fields + ], + id => 'sort_select_3', class => 'form-select form-select-sm' =%> +
+
+
+
+
+ <%= label_for sort_order_select_3 => maketext('Ordered') . ':', + class => 'col-form-label col-form-label-sm', style => 'width:4.5rem' =%> +
+ <%= select_field 'action.sort.ternary.order' => [ + [ maketext('Ascending') => 'ASC' ], + [ maketext('Descending') => 'DESC' ], + ], + id => 'sort_order_select_3', class => 'form-select form-select-sm' =%> +
+
- <%= check_box 'select-all' => 'on', id => 'select-all', - 'aria-label' => maketext('Select all jobs'), - data => { select_group => 'selected_jobs' }, - class => 'select-all form-check-input' =%> + + <%= label_for 'select-all', begin =%> + <%= check_box 'select-all' => 'on', id => 'select-all', + class => 'select-all form-check-input set-id-tooltip', + 'aria-label' => maketext('Select all jobs'), + data => { + select_group => 'selected_jobs', + bs_toggle => 'tooltip', + bs_placement => 'right', + bs_title => maketext('Select all jobs') + } =%> + + <% end =%> - <%= label_for 'select-all' => - link_to maketext('Id') => '#', class => 'sort-header', data => { sort_field => 'id' } =%> +
+ <%= link_to maketext('Id') => '#', class => 'sort-header', + data => { sort_field => 'id' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'id' =%> +
- <%= link_to maketext('Course Id') => '#', class => 'sort-header', - data => { sort_field => 'courseID' } =%> +
+ <%= link_to maketext('Course Id') => '#', class => 'sort-header', + data => { sort_field => 'courseID' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', + field => 'course_id' =%> +
- <%= link_to maketext('Task') => '#', class => 'sort-header', - data => { sort_field => 'task' } =%> +
+ <%= link_to maketext('Task') => '#', class => 'sort-header', + data => { sort_field => 'task' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'task' =%> +
- <%= link_to maketext('Created') => '#', class => 'sort-header', - data => { sort_field => 'created' } =%> +
+ <%= link_to maketext('Created') => '#', class => 'sort-header', + data => { sort_field => 'created' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'created' =%> +
- <%= link_to maketext('Started') => '#', class => 'sort-header', - data => { sort_field => 'started' } =%> +
+ <%= link_to maketext('Started') => '#', class => 'sort-header', + data => { sort_field => 'started' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'started' =%> +
- <%= link_to maketext('Finished') => '#', class => 'sort-header', - data => { sort_field => 'finished' } =%> +
+ <%= link_to maketext('Finished') => '#', class => 'sort-header', + data => { sort_field => 'finished' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'finished' =%> +
- <%= link_to maketext('State') => '#', class => 'sort-header', - data => { sort_field => 'state' } =%> +
+ <%= link_to maketext('State') => '#', class => 'sort-header', + data => { sort_field => 'state' } =%> + <%= include 'ContentGenerator/Instructor/JobManager/sort_button', field => 'state' =%> +