timeout when doing possibly unnecessary complicated counts for pagination

fixes USERS-274

test plan:
 * have a lot of users in an account (like 500)
 * go to /accounts/self/users
 * it should load, and show you how many pages there are
 * set the timeout to something absurdly low (like Setting.set('pagination_count_timeout', '1ms'))
 * go to the page again
 * it should still load the first page quickly, but the pages thingy at the bottom should only list
   1 and 2, and then an ellipsis

Change-Id: Ibc07036b39a9f4ed19d8824bb9254b23679298d1
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/234557
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Cody Cutrer <cody@instructure.com>
Product-Review: Cody Cutrer <cody@instructure.com>
This commit is contained in:
Cody Cutrer 2020-04-17 16:59:38 -06:00
parent cd27ce2ce9
commit c8b0c29361
2 changed files with 70 additions and 1 deletions

View File

@ -15,4 +15,44 @@
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require 'folio/core_ext/enumerable'
require 'folio/core_ext/enumerable'
module Folio::WillPaginate::ActiveRecord::Pagination
def paginate(options={})
if !options.has_key?(:total_entries)
scope = if ::Rails.version < '4'
self.scoped
elsif self.is_a?(::ActiveRecord::Relation)
self
elsif self < ::ActiveRecord::Base
self.all
else
self.scope
end
begin
scope.connection.transaction(requires_new: true) do
scope.connection.execute("SET LOCAL statement_timeout='#{Setting.get('pagination_count_timeout', '5s')}'")
group_values = scope.group_values
unless group_values.empty?
# total_entries left to an auto-count, but the relation being
# paginated has a grouping. we need to do a special count, lest
# self.count give us a hash instead of the integer we expect.
having_clause_empty = Rails.version < '5' ? scope.having_values.empty? : scope.having_clause.empty?
if having_clause_empty && group_values.length == 1 # multi-column distinct counts are broken right now (as of rails 4.2.5) :(
if Rails.version < '5'
options[:total_entries] = except(:group, :select).select(group_values).uniq.count
else
options[:total_entries] = except(:group, :select).select(group_values).distinct.count
end
else
options[:total_entries] = unscoped.from("(#{to_sql}) a").count
end
end
end
rescue ActiveRecord::QueryCanceled
options[:total_entries] = nil
end
end
super(options).to_a
end
end

View File

@ -0,0 +1,29 @@
#
# Copyright (C) 2020 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require_relative '../spec_helper'
describe Folio do
it "skips the count for a grouped query that takes a long time" do
User.create!
User.create!
Setting.set('pagination_count_timeout', '5ms')
result = User.group(:id).where("pg_sleep(0.1) IS NOT NULL").paginate(per_page: 1)
expect(result.length).to eq 1
expect(result.total_entries).to be_nil
end
end