diff --git a/Gemfile b/Gemfile
index bbb0cadf668..0f613abb5f1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,7 +19,7 @@ gem "good_job", "~> 3.99"
gem "gravtastic", "~> 3.2"
gem "honeybadger", "~> 5.5.1", require: false # see https://github.com/rubygems/rubygems.org/pull/4598
gem "http_accept_language", "~> 2.1"
-gem "kaminari", "~> 1.2"
+gem "pagy", "~> 9.0"
gem "launchdarkly-server-sdk", "~> 8.6"
gem "mail", "~> 2.8"
gem "octokit", "~> 9.1"
diff --git a/Gemfile.lock b/Gemfile.lock
index e56114b870d..71c75e2e13d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -326,18 +326,6 @@ GEM
faraday (~> 2.0)
faraday-follow_redirects
jwt (2.7.1)
- kaminari (1.2.2)
- activesupport (>= 4.1.0)
- kaminari-actionview (= 1.2.2)
- kaminari-activerecord (= 1.2.2)
- kaminari-core (= 1.2.2)
- kaminari-actionview (1.2.2)
- actionview
- kaminari-core (= 1.2.2)
- kaminari-activerecord (1.2.2)
- activerecord
- kaminari-core (= 1.2.2)
- kaminari-core (1.2.2)
language_server-protocol (3.17.0.3)
launchdarkly-server-sdk (8.6.0)
concurrent-ruby (~> 1.1)
@@ -483,7 +471,7 @@ GEM
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
optimist (3.1.0)
- pagy (8.4.0)
+ pagy (9.0.2)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
@@ -833,7 +821,6 @@ DEPENDENCIES
honeybadger (~> 5.5.1)
http_accept_language (~> 2.1)
importmap-rails (~> 2.0)
- kaminari (~> 1.2)
launchdarkly-server-sdk (~> 8.6)
launchy (~> 3.0)
letter_opener (~> 1.10)
@@ -855,6 +842,7 @@ DEPENDENCIES
omniauth-rails_csrf_protection (~> 1.0)
openid_connect (~> 2.3)
opensearch-ruby (~> 3.4)
+ pagy (~> 9.0)
pg (~> 1.5)
pg_query (~> 5.1)
pghero (~> 3.6)
@@ -1030,10 +1018,6 @@ CHECKSUMS
json (2.7.2) sha256=1898b5cbc81cd36c0fd4d0b7ad2682c39fb07c5ff682fc6265f678f550d4982c
json-jwt (1.16.6) sha256=ab451f9cd8743cecc4137f4170806046c1d8a6d4ee6e8570e0b5c958409b266c
jwt (2.7.1) sha256=07357cd2f180739b2f8184eda969e252d850ac996ed0a23f616e8ff0a90ae19b
- kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e
- kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909
- kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430
- kaminari-core (1.2.2) sha256=3bd26fec7370645af40ca73b9426a448d09b8a8ba7afa9ba3c3e0d39cdbb83ff
language_server-protocol (3.17.0.3) sha256=3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f
launchdarkly-server-sdk (8.6.0) sha256=c18c62d08f90b795105e98f07a24997a5628126623fadbd3b80c0d23b9409b75
launchy (3.0.1) sha256=b7fa60bda0197cf57614e271a250a8ca1f6a34ab889a3c73f67ec5d57c8a7f2c
@@ -1086,7 +1070,7 @@ CHECKSUMS
openssl (3.2.0) sha256=3c4bb8760977b4becd2819c6c2569bcf5c6f48b32b9f7a4ce1fd37f996378d14
openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80
optimist (3.1.0) sha256=81886f53ee8919f330aa30076d320d88eef9bc85aae2275376b4afb007c69260
- pagy (8.4.0) sha256=21dd68453d24e752a1bc81aef5b6f1346ff95e1c2e5b53448355f7856f3ef901
+ pagy (9.0.2) sha256=bb92efff851207a16bc359cb6a3c11278a42f260f03af1a62aae0566ea03b183
parallel (1.25.1) sha256=12e089b9aa36ea2343f6e93f18cfcebd031798253db8260590d26a7f70b1ab90
parser (3.3.4.0) sha256=8d247769c3873fe92201d591a7463384022a1a25e214853df5d6806623179e82
pg (1.5.6) sha256=4bc3ad2438825eea68457373555e3fd4ea1a82027b8a6be98ef57c0d57292b1c
diff --git a/app/assets/stylesheets/modules/nav/nav--paginated.css b/app/assets/stylesheets/modules/nav/nav--paginated.css
index 54ae75fda4d..c7669fbc49d 100644
--- a/app/assets/stylesheets/modules/nav/nav--paginated.css
+++ b/app/assets/stylesheets/modules/nav/nav--paginated.css
@@ -46,32 +46,27 @@
margin-right: 8px;
margin-left: 8px; }
-.pagination {
+nav.pagy {
margin-top: 90px;
position: relative;
width: 100%;
overflow: auto;
text-align: center; }
- .pagination a, .pagination em {
+ nav.pagy a, nav.pagy em {
margin-right: 8px;
margin-left: 8px;
font-style: normal; }
- .pagination a {
+ nav.pagy a {
color: #9da2ab;
transition-duration: 0.25s;
transition-property: color;
outline: none; }
- .pagination a:hover, .pagination a:focus, .pagination a:active {
+ nav.pagy a:hover, nav.pagy a:focus, nav.pagy a:active {
color: #141c22; }
- .pagination .previous_page.disabled, .pagination .next_page.disabled {
+ nav.pagy a:first-child:not([href]), nav.pagy a:last-child:not([href]) {
color: #9da2ab; }
- .pagination .previous_page.disabled:hover, .pagination .previous_page.disabled:focus, .pagination .previous_page.disabled:active, .pagination .next_page.disabled:hover, .pagination .next_page.disabled:focus, .pagination .next_page.disabled:active {
+ nav.pagy a:first-child:not([href]):hover, nav.pagy a:first-child:not([href]):focus, nav.pagy a:first-child:not([href]):active, nav.pagy nav.pagy a:last-child:not([href]):hover, nav.pagy nav.pagy a:last-child:not([href]):focus, nav.pagy nav.pagy a:last-child:not([href]):active {
color: #9da2ab; }
- .pagination .previous_page {
- position: absolute;
- left: 0; }
- .pagination .next_page {
- position: absolute;
- right: 0; }
- .pagination .current {
+ nav.pagy .current {
+ color: #000;
margin: 0 8px; }
diff --git a/app/controllers/api/v1/timeframe_versions_controller.rb b/app/controllers/api/v1/timeframe_versions_controller.rb
index 31007ea9a31..e8458e4a690 100644
--- a/app/controllers/api/v1/timeframe_versions_controller.rb
+++ b/app/controllers/api/v1/timeframe_versions_controller.rb
@@ -6,9 +6,7 @@ class InvalidTimeframeParameterError < StandardError; end
MAXIMUM_TIMEFRAME_QUERY_IN_DAYS = 7
def index
- render_rubygems(
- Version.created_between(from_time, to_time).page(@page)
- )
+ render_rubygems(*pagy(Version.created_between(from_time, to_time)))
end
private
@@ -35,12 +33,13 @@ def to_time
raise InvalidTimeframeParameterError, 'the "to" parameter must be iso8601 formatted'
end
- def render_rubygems(versions)
+ def render_rubygems(pagy, versions)
rubygems = versions.includes(:dependencies, :gem_download, rubygem: %i[linkset gem_download]).map do |version|
payload = version.rubygem.payload(version)
payload.merge(version.as_json)
end
+ pagy_headers_merge(pagy)
respond_to do |format|
format.json { render json: rubygems }
format.yaml { render yaml: rubygems }
diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb
index 65663f0cff9..773d74d934c 100644
--- a/app/controllers/api_keys_controller.rb
+++ b/app/controllers/api_keys_controller.rb
@@ -8,8 +8,8 @@ class ApiKeysController < ApplicationController
verify_session_before
def index
- @api_key = session.delete(:api_key)
- @api_keys = current_user.api_keys.unexpired.not_oidc.preload(ownership: :rubygem).page(@page)
+ @api_key = session.delete(:api_key)
+ @api_keys_pagy, @api_keys = pagy(current_user.api_keys.unexpired.not_oidc.preload(ownership: :rubygem))
redirect_to new_profile_api_key_path if @api_keys.empty?
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d017ae5a988..2e3c0373344 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base
include Pundit::Authorization
include ApplicationMultifactorMethods
include TraceTagger
+ include Pagy::Backend
helper ActiveSupport::NumberHelper
diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb
index 6a5009ca455..f7f3861ee5a 100644
--- a/app/controllers/news_controller.rb
+++ b/app/controllers/news_controller.rb
@@ -2,19 +2,20 @@ class NewsController < ApplicationController
before_action -> { set_page Gemcutter::NEWS_MAX_PAGES }
def show
- @rubygems = Rubygem.preload(:latest_version, :gem_download)
- .news(Gemcutter::NEWS_DAYS_LIMIT)
- .page(@page)
- .per(Gemcutter::NEWS_PER_PAGE)
+ @rubygems_pagy, @rubygems = pagy(
+ Rubygem.preload(:latest_version, :gem_download)
+ .news(Gemcutter::NEWS_DAYS_LIMIT),
+ limit: Gemcutter::NEWS_MAX_PAGES
+ )
+
limit_total_count
end
def popular
@title = t(".title")
- @rubygems = Rubygem.preload(:latest_version, :gem_download)
- .popular(Gemcutter::POPULAR_DAYS_LIMIT)
- .page(@page)
- .per(Gemcutter::NEWS_PER_PAGE)
+ @rubygems_pagy, @rubygems = pagy(Rubygem.preload(:latest_version, :gem_download)
+ .popular(Gemcutter::POPULAR_DAYS_LIMIT),
+ limit: Gemcutter::NEWS_MAX_PAGES)
limit_total_count
render :show
diff --git a/app/controllers/oidc/api_key_roles_controller.rb b/app/controllers/oidc/api_key_roles_controller.rb
index 77c1facd77a..44b73f28132 100644
--- a/app/controllers/oidc/api_key_roles_controller.rb
+++ b/app/controllers/oidc/api_key_roles_controller.rb
@@ -11,15 +11,11 @@ class OIDC::ApiKeyRolesController < ApplicationController
before_action :set_page, only: :index
def index
- @api_key_roles = current_user.oidc_api_key_roles.active.includes(:provider)
- .page(@page)
- .strict_loading
+ @api_key_roles_pagy, @api_key_roles = pagy(current_user.oidc_api_key_roles.active.includes(:provider).strict_loading)
end
def show
- @id_tokens = @api_key_role.id_tokens.order(id: :desc).includes(:api_key)
- .page(0).per(10)
- .strict_loading
+ @id_tokens_pagy, @id_tokens = pagy(@api_key_role.id_tokens.order(id: :desc).includes(:api_key).strict_loading, limit: 10)
respond_to do |format|
format.json do
render json: @api_key_role
diff --git a/app/controllers/oidc/providers_controller.rb b/app/controllers/oidc/providers_controller.rb
index 5bd58d3c759..996199ce01f 100644
--- a/app/controllers/oidc/providers_controller.rb
+++ b/app/controllers/oidc/providers_controller.rb
@@ -8,8 +8,8 @@ class OIDC::ProvidersController < ApplicationController
before_action :set_page, only: :index
def index
- providers = OIDC::Provider.strict_loading.page(@page)
- render OIDC::Providers::IndexView.new(providers:)
+ providers_pagy, providers = pagy(OIDC::Provider.strict_loading)
+ render OIDC::Providers::IndexView.new(providers_pagy:, providers:)
end
def show
diff --git a/app/controllers/ownership_calls_controller.rb b/app/controllers/ownership_calls_controller.rb
index 67fd36844cf..bff7ec02f04 100644
--- a/app/controllers/ownership_calls_controller.rb
+++ b/app/controllers/ownership_calls_controller.rb
@@ -13,9 +13,10 @@ class OwnershipCallsController < ApplicationController
def index
set_page
- @ownership_calls = OwnershipCall.opened.includes(:user, rubygem: %i[latest_version gem_download]).order(created_at: :desc)
- .page(@page)
- .per(Gemcutter::OWNERSHIP_CALLS_PER_PAGE)
+ @ownership_calls_pagy, @ownership_calls = pagy(
+ OwnershipCall.opened.includes(:user, rubygem: %i[latest_version gem_download]).order(created_at: :desc),
+ limit: Gemcutter::OWNERSHIP_CALLS_PER_PAGE
+ )
end
def create
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index 1adbf233817..dd79909ca95 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -53,8 +53,8 @@ def adoptions
end
def security_events
- @security_events = current_user.events.order(id: :desc).page(params[:page]).per(50)
- render Profiles::SecurityEventsView.new(security_events: @security_events)
+ @security_events_pagy, @security_events = pagy(current_user.events.order(id: :desc), limit: 50)
+ render Profiles::SecurityEventsView.new(security_events_pagy: @security_events_pagy, security_events: @security_events)
end
private
diff --git a/app/controllers/reverse_dependencies_controller.rb b/app/controllers/reverse_dependencies_controller.rb
index 706668aeff9..2a4eabd5481 100644
--- a/app/controllers/reverse_dependencies_controller.rb
+++ b/app/controllers/reverse_dependencies_controller.rb
@@ -11,6 +11,6 @@ def index
.preload(:gem_download, :latest_version)
@reverse_dependencies = @reverse_dependencies.legacy_search(params[:rdeps_query]) if params[:rdeps_query].is_a?(String)
- @reverse_dependencies = @reverse_dependencies.page(@page).without_count
+ @reverse_dependencies_pagy, @reverse_dependencies = pagy_countless(@reverse_dependencies)
end
end
diff --git a/app/controllers/rubygems_controller.rb b/app/controllers/rubygems_controller.rb
index 2d0b9eb8709..17648f19608 100644
--- a/app/controllers/rubygems_controller.rb
+++ b/app/controllers/rubygems_controller.rb
@@ -11,7 +11,7 @@ def index
respond_to do |format|
format.html do
@letter = Rubygem.letterize(gem_params[:letter])
- @gems = Rubygem.letter(@letter).includes(:latest_version, :gem_download).page(@page)
+ @gems_pagy, @gems = pagy(Rubygem.letter(@letter).includes(:latest_version, :gem_download), limit: 2)
end
format.atom do
@versions = Version.published.limit(Gemcutter::DEFAULT_PAGINATION)
@@ -36,8 +36,8 @@ def show
def security_events
authorize @rubygem, :show_events?
- @security_events = @rubygem.events.order(id: :desc).page(params[:page]).per(50)
- render Rubygems::SecurityEventsView.new(rubygem: @rubygem, security_events: @security_events)
+ @security_events_pagy, @security_events = pagy(@rubygem.events.order(id: :desc), limit: 50)
+ render Rubygems::SecurityEventsView.new(rubygem: @rubygem, security_events_pagy: @security_events_pagy, security_events: @security_events)
end
private
diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb
index 6cf5b058b32..bae489ee5ad 100644
--- a/app/controllers/searches_controller.rb
+++ b/app/controllers/searches_controller.rb
@@ -6,7 +6,8 @@ def show
@error_msg, @gems = ElasticSearcher.new(params[:query], page: @page).search
return unless @gems
- set_total_pages if @gems.total_count > Gemcutter::SEARCH_MAX_PAGES * Rubygem.default_per_page
+ @gems_pagy = Pagy.new_from_searchkick(@gems)
+ set_total_pages if @gems.total_count > 10_000
exact_match = Rubygem.name_is(params[:query]).first
@yanked_gem = exact_match unless exact_match&.indexed_versions?
@yanked_filter = true if params[:yanked] == "true"
diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb
index 8b76902f9fd..b1957c7833f 100644
--- a/app/controllers/stats_controller.rb
+++ b/app/controllers/stats_controller.rb
@@ -5,7 +5,8 @@ def index
@number_of_gems = Rubygem.total_count
@number_of_users = User.count
@number_of_downloads = GemDownload.total_count
- @most_downloaded = Rubygem.by_downloads.includes(:gem_download).page(@page).per(Gemcutter::STATS_PER_PAGE)
+ @most_downloaded_pagy,
+ @most_downloaded = pagy(Rubygem.by_downloads.includes(:gem_download), limit: Gemcutter::STATS_PER_PAGE)
@most_downloaded_count = GemDownload.most_downloaded_gem_count
limit_total_count
end
diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb
index a2ce84c89f1..d4d9c196e86 100644
--- a/app/controllers/versions_controller.rb
+++ b/app/controllers/versions_controller.rb
@@ -3,7 +3,7 @@ class VersionsController < ApplicationController
def index
set_page
- @versions = @rubygem.versions.by_position.page(@page).per(Gemcutter::VERSIONS_PER_PAGE)
+ @versions_pagy, @versions = pagy(@rubygem.versions.by_position, limit: Gemcutter::VERSIONS_PER_PAGE)
end
def show
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 62447d8a786..36de3576ecf 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,5 +1,6 @@
module ApplicationHelper
include BetterHtml::Helpers
+ include Pagy::Frontend
def page_title
combo = "#{t :title} | #{t :subtitle}"
@@ -59,12 +60,6 @@ def active?(path)
"is-active" if request.path_info == path
end
- # replacement for Kaminari::ActionViewExtension#paginate
- # only shows `next` and `prev` links and not page numbers, saving a COUNT(DISTINCT ..) query
- def plain_paginate(items)
- render "layouts/plain_paginate", items: items
- end
-
def content_for_title(title, title_url)
return title unless title_url
link_to title, title_url, class: "t-link--black"
diff --git a/app/views/api_keys/index.html.erb b/app/views/api_keys/index.html.erb
index c1816ee1a9c..a7e843334f3 100644
--- a/app/views/api_keys/index.html.erb
+++ b/app/views/api_keys/index.html.erb
@@ -11,7 +11,7 @@
- <%= page_entries_info @api_keys, entry_name: 'API keys' %>
+ <%== pagy_info @api_keys_pagy, item_name: 'API keys' %>
<%= button_to t(".new_key"), new_profile_api_key_path, method: "get", class: "form__submit" %>
<% if current_user.oidc_api_key_roles.any? %> diff --git a/app/views/components/events/table_component.rb b/app/views/components/events/table_component.rb index e6955d33857..53e26d6fe39 100644 --- a/app/views/components/events/table_component.rb +++ b/app/views/components/events/table_component.rb @@ -9,16 +9,17 @@ class Events::TableComponent < ApplicationComponent extend Phlex::Rails::HelperMacros register_value_helper :current_user - register_value_helper :page_entries_info - register_value_helper :paginate + register_value_helper :pagy_info + register_value_helper :pagy_nav extend Dry::Initializer + option :security_events_pagy option :security_events def view_template header(class: "gems__header push--s") do - p(class: "gems__meter l-mb-0") { plain page_entries_info(security_events) } + p(class: "gems__meter l-mb-0") { plain pagy_info(security_events_pagy, item_name: "security events") } end if security_events.any? @@ -39,7 +40,7 @@ def view_template end end - plain paginate(security_events) + plain pagy_nav(security_events_pagy) if security_events_pagy.pages > 1 end private diff --git a/app/views/layouts/_plain_paginate.html.erb b/app/views/layouts/_plain_paginate.html.erb index dd0a76bed34..decbf206a4b 100644 --- a/app/views/layouts/_plain_paginate.html.erb +++ b/app/views/layouts/_plain_paginate.html.erb @@ -1,4 +1,4 @@ diff --git a/app/views/news/show.html.erb b/app/views/news/show.html.erb index 62815a3dec3..7959c0973a7 100644 --- a/app/views/news/show.html.erb +++ b/app/views/news/show.html.erb @@ -6,8 +6,8 @@<%= page_entries_info @rubygems %>
+<%== pagy_info @rubygems_pagy, item_name: 'gems' %>
<%= page_entries_info(@api_key_roles) %>
+<%== pagy_info(@api_key_roles_pagy) %>
<%= page_entries_info(@id_tokens) %>
+<%== pagy_info(@id_tokens_pagy, item_name: "ID Tokens") %>
- <%= page_entries_info @ownership_calls, entry_name: 'ownership calls' %> + <%== pagy_info @ownership_calls_pagy, item_name: 'ownership calls' %>
<%= page_entries_info(@gems, :entry_name => 'gem') %>
+<%== pagy_info(@gems_pagy, :item_name => 'gem') %>
<%= page_entries_info @most_downloaded, entry_name: 'gem' %>
+<%== pagy_info @most_downloaded_pagy, item_name: 'gem' %>
<%= t('.not_hosted_notice') %>
<% else %> -