[Rt-commit] rt branch 5.0/update-search-result-paging created. rt-5.0.3-221-gac43e91bc0

BPS Git Server git at git.bestpractical.com
Mon Jan 9 22:03:09 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt".

The branch, 5.0/update-search-result-paging has been created
        at  ac43e91bc0041b976bf7d719858700f55a340863 (commit)

- Log -----------------------------------------------------------------
commit ac43e91bc0041b976bf7d719858700f55a340863
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Mon Jan 9 17:00:58 2023 -0500

    Add box to jump to search results page

diff --git a/share/html/Elements/CollectionListPaging b/share/html/Elements/CollectionListPaging
index 94b0455b40..2284a8d572 100644
--- a/share/html/Elements/CollectionListPaging
+++ b/share/html/Elements/CollectionListPaging
@@ -99,6 +99,24 @@ else{
         }
     }
 }
-
 $m->out(qq{</div>});
+
+# Clear Page before writing hidden values for the jump form
+delete $URLParams->{'Page'} if $URLParams->{'Page'};
 </%INIT>
+<div class="page-jump mx-auto">
+  <form method="post" action="<%RT->Config->Get('WebPath')%>/Search/Results.html">
+% foreach my $key ( keys(%$URLParams) ) {
+<input type="hidden" class="hidden" name="<%$key%>" value="<% defined($URLParams->{$key})?$URLParams->{$key}:'' %>" />
+% }
+  <div class="form-row">
+    <div class="label col-6"><&|/l&>Jump to page</&>:</div>
+    <div class="col-4">
+      <input type="text" class="form-control" name="Page" value="" />
+    </div>
+    <div class="col-2">
+      <input type="submit" class="button btn btn-primary form-control" value="<&|/l&>Go</&>" />
+    </div>
+  </div>
+  </form>
+</div>
diff --git a/share/static/css/elevator-light/ticket-lists.css b/share/static/css/elevator-light/ticket-lists.css
index d5c9631f14..38ee1323b6 100644
--- a/share/static/css/elevator-light/ticket-lists.css
+++ b/share/static/css/elevator-light/ticket-lists.css
@@ -52,3 +52,7 @@ table.queue-summary th.collection-as-table {
 li.page-item > a.rt-page-link {
     color: #3858a3;
 }
+
+.page-jump {
+    width: 200px;
+}

commit 96b72e01e02e6043028e595c03692ba630ac2c52
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Tue Jan 3 09:44:10 2023 -0500

    Update search results to use Bootstrap pagination styles
    
    For large sets of search results, this update also replaces
    the huge block of numbers with a simpler begining, middle,
    end set of page numbers. This still allows navigation without
    the giant list of number ranges.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index da6382ec27..144250c77d 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -5303,6 +5303,71 @@ sub GetDashboards {
     return \%dashboards;
 }
 
+=head2 BuildSearchResultPagination
+
+Accepts a Data::Page object loaded with information about a set of
+search results.
+
+Returns an array with the pages from that set to include when displaying
+pagination for those search results. This array can then be used to
+generate the desired links and other UI.
+
+=cut
+
+sub BuildSearchResultPagination {
+    my $pager = shift;
+    my @pages;
+
+    # For 10 or less, show all pages in a line, no breaks
+    if ( $pager->last_page < 11 ) {
+        for ( my $p = 1; $p <= $pager->last_page; $p++ ) {
+            push @pages, $p;
+        }
+    }
+    else {
+        # For more pages, need to insert ellipsis for breaks
+        # This creates 1 2 3...10 11 12...51 52 53
+        @pages = ( 1, 2, 3 );
+
+        if ( $pager->current_page() == 3 ) {
+            # When on page 3, show 4 so you can keep going
+            push @pages, ( 4 );
+        }
+        elsif ( $pager->current_page() == 4 ) {
+            # Handle 4 and 5 without ellipsis
+            push @pages, ( 4, 5 );
+        }
+        elsif ( $pager->current_page() == 5 ) {
+            # Handle 4 and 5 without ellipsis
+            push @pages, ( 4, 5, 6 );
+        }
+        elsif ( $pager->current_page() > 5 && $pager->current_page() < ($pager->last_page - 4) ) {
+            push @pages, ( 'ellipsis',
+            $pager->current_page() - 1, $pager->current_page(), $pager->current_page() + 1 );
+        }
+
+        push @pages, 'ellipsis';
+
+        # Add padding at the end, the reverse of the above
+        if ( $pager->current_page() == ($pager->last_page - 2) ) {
+            push @pages, $pager->last_page - 3;
+        }
+
+        if ( $pager->current_page() == ($pager->last_page - 3) ) {
+            push @pages, ( $pager->last_page - 4, $pager->last_page - 3 );
+        }
+
+        if ( $pager->current_page() == ($pager->last_page - 4) ) {
+            push @pages, ( $pager->last_page - 5, $pager->last_page - 4, $pager->last_page - 3 );
+        }
+
+        # Add the end of the list
+        push @pages, ( $pager->last_page - 2, $pager->last_page -1, $pager->last_page );
+    }
+
+    return @pages;
+}
+
 package RT::Interface::Web;
 RT::Base->_ImportOverlays();
 
diff --git a/share/html/Elements/CollectionListPaging b/share/html/Elements/CollectionListPaging
index 1ba778c499..94b0455b40 100644
--- a/share/html/Elements/CollectionListPaging
+++ b/share/html/Elements/CollectionListPaging
@@ -60,51 +60,44 @@ $BaseURL = $m->interp->apply_escapes($BaseURL, 'h');
 
 $m->out(qq{<div class="paging">});
 if ($Pages == 1) {
-  $m->out(loc('Page 1 of 1'));
+    $m->out(loc('Page 1 of 1'));
 }
 else{
-  $m->out(loc('Page') . ' ');
+    $m->out(qq{<ul class="pagination justify-content-center">});
+    use Data::Page;
 
-  use Data::Page;
-  use Data::Page::Pageset;
+    my $pager = Data::Page->new();
+    $pager->total_entries($TotalFound);
+    $pager->entries_per_page($Rows);
+    $pager->current_page($CurrentPage);
 
-  my $pager = Data::Page->new();
-  $pager->total_entries($TotalFound);
-  $pager->entries_per_page($Rows);
-  $pager->current_page($CurrentPage);
-  my $pageset = Data::Page::Pageset->new($pager);
+    my @pages = BuildSearchResultPagination( $pager );
 
-  for my $chunk ( $pageset->total_pagesets ) {
-    $m->out(qq{<span class="pagenum">});
-    if ( $chunk->is_current ) {
-        for my $number ( $chunk->first .. $chunk->last ) {
-            if ( $number == $CurrentPage ) {
-              $m->out(qq{<span class="currentpage">$number</span> });
-            }
-            else {
-              my $qs = $m->interp->apply_escapes(
+    foreach my $page ( @pages ) {
+
+        my $active_item = '';
+        $active_item = " active" if ( $page =~ /^\d+/ && $page == $pager->current_page() );
+
+        if ( $page eq 'ellipsis' ) {
+            $m->out(qq{<li class="page-item disabled">});
+            $m->out(qq{<a class="page-link" href="#" tabindex="-1">...</a>});
+            $m->out(qq{</li>});
+            next;
+        }
+        else {
+            # Build the link
+            my $query_string = $m->interp->apply_escapes(
                 $m->comp('/Elements/QueryString',
-                  %$URLParams,
-                  $PageParam => $number
-                ),
+                    %$URLParams,
+                    $PageParam => $page, ),
                 'h',
-              );
-              $m->out(qq{<a href="$BaseURL$qs">$number</a> });
-            }
+            );
+
+            $m->out(qq{<li class="page-item$active_item">});
+            $m->out(qq{<a class="page-link rt-page-link" href="$BaseURL$query_string">$page</a>});
+            $m->out(qq{</li>});
         }
-    } else {
-      my $qs = $m->interp->apply_escapes(
-        $m->comp('/Elements/QueryString',
-          %$URLParams,
-          $PageParam => $chunk->first,
-        ),
-        'h',
-      );
-      $m->out(qq{<a href="$BaseURL$qs">$chunk</a>});
     }
-    $m->out(qq{</span>});
-  }
-
 }
 
 $m->out(qq{</div>});
diff --git a/share/static/css/elevator-light/misc.css b/share/static/css/elevator-light/misc.css
index 6e1d5c0e0a..6a047883ef 100644
--- a/share/static/css/elevator-light/misc.css
+++ b/share/static/css/elevator-light/misc.css
@@ -142,11 +142,6 @@ h1#transaction-extra-info {
     padding-top: 0.4rem;
 }
 
-/* Prevent page links from running off the side of the page for Firefox */
-span.pagenum {
-    display: inline-block;
-}
-
 /* Do not change background color on click of dropdown items like "Show
  * full headers" in ticket history */
 .btn-group.dropdown .dropdown-item:active {
diff --git a/share/static/css/elevator-light/ticket-lists.css b/share/static/css/elevator-light/ticket-lists.css
index cd30418fec..d5c9631f14 100644
--- a/share/static/css/elevator-light/ticket-lists.css
+++ b/share/static/css/elevator-light/ticket-lists.css
@@ -49,19 +49,6 @@ table.queue-summary th.collection-as-table {
   vertical-align: middle;
 }
 
-.pagenum *,
-.paging a.nav {
-    padding: .5em;
-}
-
-.currentpage {
-    text-decoration: none;
-    font-weight: bold;
-    background: #eee;
-}
-
-div.paging {
-    font-size: 1.07em;
-    text-align: center;
-    padding-bottom: 1em;
+li.page-item > a.rt-page-link {
+    color: #3858a3;
 }

commit e3c7d7c01822e2d96f0ad37209a073dba28b5e45
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Thu Jan 5 14:15:48 2023 -0500

    Convert to preferred constructor for Data::Page
    
    The documentation for Data::Page says the constructor that
    accepts arguments is deprecated, so switch to using the
    provided methods to set page data on the pager object.

diff --git a/share/html/Elements/CollectionListPaging b/share/html/Elements/CollectionListPaging
index 766e41d57d..1ba778c499 100644
--- a/share/html/Elements/CollectionListPaging
+++ b/share/html/Elements/CollectionListPaging
@@ -68,7 +68,10 @@ else{
   use Data::Page;
   use Data::Page::Pageset;
 
-  my $pager = Data::Page->new($TotalFound, $Rows, $CurrentPage);
+  my $pager = Data::Page->new();
+  $pager->total_entries($TotalFound);
+  $pager->entries_per_page($Rows);
+  $pager->current_page($CurrentPage);
   my $pageset = Data::Page::Pageset->new($pager);
 
   for my $chunk ( $pageset->total_pagesets ) {

commit 3fcbe1f62bbb065a9e7b4c53d18192192f06b195
Author: Jim Brandt <jbrandt at bestpractical.com>
Date:   Fri Dec 16 16:28:55 2022 -0500

    Provide a simple framework for showing user messages
    
    During processing, messages can be stashed in notes,
    then Footer writes any messages to the page at the end
    for growl to then show to the user.

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 2c06b49972..5b63911441 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -2914,7 +2914,7 @@ sub SimpleQuery {
 
     # Show end user something if query failed.
     if ($HTML::Mason::Commands::m) {
-        $HTML::Mason::Commands::m->notes( 'SQLError' => 1 );
+        $HTML::Mason::Commands::m->notes( 'Message:SQLTimeout' => 1 );
     }
     return $ret;
 }
diff --git a/lib/RT/System.pm b/lib/RT/System.pm
index b07ea86abf..a3cda3e504 100644
--- a/lib/RT/System.pm
+++ b/lib/RT/System.pm
@@ -448,6 +448,38 @@ sub ExternalStorageURLFor {
     return $self->ExternalStorage->DownloadURLFor($Object);
 }
 
+# Catalog of message codes and user messages
+our %USER_MESSAGES;
+%USER_MESSAGES = (
+    'SQLTimeout' => 'Your query exceeded the maximum run time and was stopped. Try modifying your query to improve the performance or contact your RT admin.',
+);
+
+=head1 UserMessages
+
+Returns a hash with keys of message codes and values with corresonding user
+messages.
+
+To add messages, create entries in C<%USER_MESSAGES> with an appropriate
+message code as the key.
+
+To trigger display of that message, add an entry to notes, prefixed with
+C<Message:>, like:
+
+    if ( $something_happened ) {
+        $HTML::Mason::Commands::m->notes('Message:SQLTimeout' => 1 );
+    }
+
+For the web UI, C<Elements/Footer> will pick that up and display the message
+to the user in the browser.
+
+=cut
+
+sub UserMessages {
+    my $self = shift;
+    # Return a copy to avoid it from changing by accident.
+    return { %USER_MESSAGES };
+}
+
 RT::Base->_ImportOverlays();
 
 1;
diff --git a/share/html/Elements/Footer b/share/html/Elements/Footer
index bbf3126586..0de4f1972b 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Elements/Footer
@@ -78,15 +78,28 @@
 </pre>
 % }
     </div>
-% if ( $m->notes('SQLError') ) {
-    <script type="text/javascript">
-    jQuery( function() {
-      jQuery.jGrowl(<% loc('Page content might be inaccurate because of SQL error. Please contact your admin, they can find more details in the logs.') |j %>, { sticky: true, themeState: 'none' });
-    } );
-    </script>
-% }
+<script type="text/javascript">
+RT.UserMessages = <% JSON( \%UserMessages ) |n%>;
+</script>
   </body>
 </html>
+<%init>
+my %UserMessages;
+
+# Check for any messages from the page processing stashed in notes.
+# We'll write them to the page so growl can find and display them.
+
+foreach my $note ( keys %{ $m->notes } ) {
+    if ( my ($message) = $note =~ /^Message\:(\w+)/ ) {
+        if ( my $user_message = RT->System->UserMessages->{$message} ) {
+            $UserMessages{$message} = loc( $user_message );
+        }
+        else {
+            RT->Logger->warning("Couldn't find user message for $message");
+        }
+    }
+}
+</%init>
 <%ARGS>
 $Debug => 0
 $Menu => 1
diff --git a/share/static/js/util.js b/share/static/js/util.js
index e7b5d8c75a..6e7deb628f 100644
--- a/share/static/js/util.js
+++ b/share/static/js/util.js
@@ -1432,3 +1432,11 @@ function toggleTransactionDetails () {
 
     return false;
 }
+
+// Use Growl to show any UserMessages written to the page
+jQuery( function() {
+    var userMessages = RT.UserMessages;
+    for (var key in userMessages) {
+        jQuery.jGrowl(userMessages[key], { sticky: true, themeState: 'none' });
+    }
+} );

commit 78b90cf3ecf871fd9f217a4983b4debdbc1f067d
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 23 05:42:01 2022 +0800

    Show end users a hint about SQL error

diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index 6f29cb2856..2c06b49972 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -2907,6 +2907,18 @@ sub _CanonilizeObjectCustomFieldValue {
     }
 }
 
+sub SimpleQuery {
+    my $self = shift;
+    my $ret  = $self->SUPER::SimpleQuery(@_);
+    return $ret if $ret;
+
+    # Show end user something if query failed.
+    if ($HTML::Mason::Commands::m) {
+        $HTML::Mason::Commands::m->notes( 'SQLError' => 1 );
+    }
+    return $ret;
+}
+
 __PACKAGE__->FinalizeDatabaseType;
 
 RT::Base->_ImportOverlays();
diff --git a/share/html/Elements/Footer b/share/html/Elements/Footer
index 556efcc950..bbf3126586 100644
--- a/share/html/Elements/Footer
+++ b/share/html/Elements/Footer
@@ -78,6 +78,13 @@
 </pre>
 % }
     </div>
+% if ( $m->notes('SQLError') ) {
+    <script type="text/javascript">
+    jQuery( function() {
+      jQuery.jGrowl(<% loc('Page content might be inaccurate because of SQL error. Please contact your admin, they can find more details in the logs.') |j %>, { sticky: true, themeState: 'none' });
+    } );
+    </script>
+% }
   </body>
 </html>
 <%ARGS>

commit d3bcbf4b0327bc1f0f587a64331c9518f62290cd
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Thu Nov 17 06:01:28 2022 +0800

    Add $DatabaseQueryTimeout to abort long running SQL queries

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index ecb40b980d..6c80eaa26b 100644
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -281,6 +281,24 @@ during upgrades.
 
 Set($DatabaseAdmin, "@DB_DBA@");
 
+=item C<$DatabaseQueryTimeout>
+
+Time in seconds to allow a single database query to run before
+timing out. This helps prevent performance penalties for expensive,
+long-running queries. This value, if set, should be less than any
+timeout settings in your web server (Apache, etc.) so the user can
+receive a message rather than a server timeout error.
+
+It's disabled by default and only works with MariaDB/MySQL/PostgreSQL.
+
+If you have command-line scripts that may require a longer-running query,
+you can set the environment variable C<RT_DATABASE_QUERY_TIMEOUT>
+to override the value set in RT's configuration.
+
+=cut
+
+Set($DatabaseQueryTimeout, undef);
+
 =back
 
 
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 6c3272fd36..ea99df798d 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -1897,6 +1897,19 @@ our %META;
     DashboardSubject => {
         Widget => '/Widgets/Form/String',
     },
+    DatabaseQueryTimeout => {
+        Immutable => 1,
+        Widget    => '/Widgets/Form/String',
+        PostLoadCheck => sub {
+            my $self = shift;
+            if ( defined $ENV{RT_DATABASE_QUERY_TIMEOUT} && length $ENV{RT_DATABASE_QUERY_TIMEOUT} ) {
+                RT->Logger->debug(
+                    "Env RT_DATABASE_QUERY_TIMEOUT is defined, setting DatabaseQueryTimeout to '$ENV{RT_DATABASE_QUERY_TIMEOUT}'."
+                );
+                $self->Set('DatabaseQueryTimeout', $ENV{RT_DATABASE_QUERY_TIMEOUT} );
+            }
+        },
+    },
     DefaultErrorMailPrecedence => {
         Widget => '/Widgets/Form/String',
     },
diff --git a/lib/RT/Handle.pm b/lib/RT/Handle.pm
index f557832669..6f29cb2856 100644
--- a/lib/RT/Handle.pm
+++ b/lib/RT/Handle.pm
@@ -127,14 +127,27 @@ sub Connect {
         %args,
     );
 
+    my $timeout = RT->Config->Get('DatabaseQueryTimeout');
     if ( $db_type eq 'mysql' ) {
         # set the character set
         $self->dbh->do("SET NAMES 'utf8mb4'");
+        if ( defined $timeout && length $timeout ) {
+            if ( $self->_IsMariaDB ) {
+                $self->dbh->do("SET max_statement_time = $timeout");
+            }
+            else {
+                # max_execution_time is defined in milliseconds
+                $self->dbh->do( "SET max_execution_time = " . int( $timeout * 1000 ) );
+            }
+        }
     }
     elsif ( $db_type eq 'Pg' ) {
         my $version = $self->DatabaseVersion;
         ($version) = $version =~ /^(\d+\.\d+)/;
         $self->dbh->do("SET bytea_output = 'escape'") if $version >= 9.0;
+        # statement_timeout is defined in milliseconds
+        $self->dbh->do( "SET statement_timeout = " . int( $timeout * 1000 ) )
+            if defined $timeout && length $timeout;
     }
 
     $self->dbh->{'LongReadLen'} = RT->Config->Get('MaxAttachmentSize');

-----------------------------------------------------------------------


hooks/post-receive
-- 
rt


More information about the rt-commit mailing list