Skip to content

Commit

Permalink
Merge pull request #2299 from alphagov/publications-page/pagination
Browse files Browse the repository at this point in the history
Add pagination to publication page
  • Loading branch information
mtaylorgds authored Aug 29, 2024
2 parents 0acdf2e + 827341b commit ef3a72c
Show file tree
Hide file tree
Showing 16 changed files with 346 additions and 20 deletions.
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ $govuk-page-width: 1140px;
@import 'govuk_publishing_components/components/layout-header';
@import 'govuk_publishing_components/components/lead-paragraph';
@import 'govuk_publishing_components/components/notice';
@import 'govuk_publishing_components/components/previous-and-next-navigation';
@import 'govuk_publishing_components/components/search';
@import 'govuk_publishing_components/components/skip-link';
@import 'govuk_publishing_components/components/success-alert';
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/root_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ def index
assigned_to_filter: assignee_filter,
format_filter:,
title_filter:,
page: filter_params_hash[:page],
)
end

private

def filter_params
params.permit(:assignee_filter, :format_filter, :title_filter, states_filter: [])
params.permit(:page, :assignee_filter, :format_filter, :title_filter, states_filter: [])
end
end
158 changes: 158 additions & 0 deletions app/helpers/pagination_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
module PaginationHelper
class << self
LARGE_NUMBER_OF_PAGES = 6

def pagination_hash(current_page:, total_pages:, path:)
return if total_pages == 1

self.current_page = current_page
self.total_pages = total_pages
self.path = normalise_path(path, current_page)

previous_page = current_page - 1 if current_page >= 2
next_page = current_page + 1 if current_page < total_pages

previous_page_href = build_path_for(current_page - 1)
self.previous_href = previous_page.present? ? previous_page_href : nil

next_page_href = build_path_for(current_page + 1) if next_page.present?
self.next_href = next_page_href.presence

if total_pages >= LARGE_NUMBER_OF_PAGES
large_page_number_hash
else
small_page_number_hash
end
end

private

attr_accessor :path, :previous_href, :next_href, :current_page, :total_pages

def normalise_path(path, current_page)
if url_has_page_param?(path)
path
elsif url_has_a_query_string?(path) && !url_has_an_anchor?(path)
path + "&page=#{current_page}"
elsif url_has_a_query_string?(path) && url_has_an_anchor?(path)
path.gsub("#", "&page=#{current_page}#")
elsif url_has_an_anchor?(path)
path.gsub("#", "?page=#{current_page}#")
else
path + "?page=#{current_page}"
end
end

def url_has_page_param?(path)
path.include?("page=#{current_page}")
end

def url_has_a_query_string?(path)
path.include?("?")
end

def url_has_an_anchor?(path)
path.include?("#")
end

def build_path_for(page)
path.gsub("page=#{current_page}", "page=#{page}")
end

def small_page_number_hash
items = []

[*1..total_pages].map do |page|
items << {
href: build_path_for(page),
current: page == current_page,
}
end

{
previous_href:,
next_href:,
items:,
}
end

def large_page_number_hash
items = [
first_page_hash,
first_elipsis_hash,
middle_pages_array,
second_elipsis_hash,
last_page_hash,
]
.flatten
.compact

{
previous_href:,
next_href:,
items:,
}
end

def first_page_hash
{
href: build_path_for(1),
label: "1",
current: current_page == 1,
aria_label: "Page 1",
}
end

def first_elipsis_hash
return unless current_page >= 4

{ ellipses: true }
end

def middle_pages_array
get_page_numbers.map do |page|
{
href: build_path_for(page),
label: page.to_s,
current: current_page == page,
aria_label: "Page #{page}",
}
end
end

def get_page_numbers
first_page = 1
second_page = 2
penultimate_page = total_pages - 1
last_page = total_pages

case current_page
when first_page
[first_page + 1, first_page + 2, first_page + 3]
when second_page
[second_page, second_page + 1, second_page + 2]
when last_page
[last_page - 3, last_page - 2, last_page - 1]
when penultimate_page
[penultimate_page - 2, penultimate_page - 1, penultimate_page]
else
[current_page - 1, current_page, current_page + 1]
end
end

def second_elipsis_hash
return unless total_pages - current_page >= 3

{ ellipses: true }
end

def last_page_hash
{
href: build_path_for(total_pages),
label: total_pages.to_s,
current: current_page == total_pages,
aria_label: "Page #{total_pages}",
}
end
end
end
9 changes: 4 additions & 5 deletions app/presenters/filtered_editions_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
class FilteredEditionsPresenter
ITEMS_PER_PAGE = 20

def initialize(states_filter: [], assigned_to_filter: nil, format_filter: nil, title_filter: nil)
def initialize(states_filter: [], assigned_to_filter: nil, format_filter: nil, title_filter: nil, page: nil)
@states_filter = states_filter || []
@assigned_to_filter = assigned_to_filter
@format_filter = format_filter
@title_filter = title_filter
@page = page
end

def available_users
Expand All @@ -20,9 +21,7 @@ def editions
result = apply_assigned_to_filter(result)
result = apply_title_filter(result)
result = result.where.not(_type: "PopularLinksEdition")
# Sets a temporary limit of one page and twenty items
# Pagination to follow
result.page(1).per(ITEMS_PER_PAGE)
result.order_by(%w[updated_at desc]).page(@page).per(ITEMS_PER_PAGE)
end

private
Expand Down Expand Up @@ -61,5 +60,5 @@ def apply_title_filter(editions)
editions.title_contains(title_filter)
end

attr_reader :states_filter, :assigned_to_filter, :format_filter, :title_filter
attr_reader :states_filter, :assigned_to_filter, :format_filter, :title_filter, :page
end
60 changes: 60 additions & 0 deletions app/views/components/_pagination.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<%
id ||= "pagination-#{SecureRandom.hex(4)}"
aria_label ||= "Pagination"
items ||= []
previous_href ||= false
next_href ||= false
%>

<nav id="<% id %>" class="app-c-pagination govuk-pagination" role="navigation" aria-label="<%= aria_label %>">
<% if previous_href %>
<div class="govuk-pagination__prev">
<a class="govuk-link govuk-pagination__link" href="<%= previous_href %>" rel="prev">
<svg class="govuk-pagination__icon govuk-pagination__icon--prev" xmlns="http://www.w3.org/2000/svg" height="13" width="15" aria-hidden="true" focusable="false" viewBox="0 0 15 13">
<path d="m6.5938-0.0078125-6.7266 6.7266 6.7441 6.4062 1.377-1.449-4.1856-3.9768h12.896v-2h-12.984l4.2931-4.293-1.414-1.414z"></path>
</svg>
<span class="govuk-pagination__link-title">Previous<span class="govuk-visually-hidden"> page</span></span>
</a>
</div>
<% end %>

<ul class="govuk-pagination__list">
<% items.each_with_index do | item, index | %>
<%
item_label = item[:label] ? item[:label] : index + 1
item_aria_label = item[:label] ? item[:label] : "Page #{item_label}"
item_aria_label = (item[:aria_label].presence || item_aria_label)
list_item_classes = %w[govuk-pagination__item]
list_item_classes << "govuk-pagination__item--current" if item[:current]
list_item_classes << "govuk-pagination__item--ellipses" if item[:ellipses]
%>

<%= tag.li class: list_item_classes do %>
<% if item[:ellipses] %>
&ctdot;
<% else %>
<%= tag.a(
item_label,
class: "govuk-link govuk-pagination__link",
href: item[:href],
aria: {
label: item_aria_label,
current: item[:current] ? "page" : nil,
},
) %>
<% end %>
<% end %>
<% end %>
</ul>

<% if next_href %>
<div class="govuk-pagination__next">
<a class="govuk-link govuk-pagination__link" href="<%= next_href %>" rel="next">
<span class="govuk-pagination__link-title">Next<span class="govuk-visually-hidden"> page</span></span>
<svg class="govuk-pagination__icon govuk-pagination__icon--next" xmlns="http://www.w3.org/2000/svg" height="13" width="15" aria-hidden="true" focusable="false" viewBox="0 0 15 13">
<path d="m8.107-0.0078125-1.4136 1.414 4.2926 4.293h-12.986v2h12.896l-4.1855 3.9766 1.377 1.4492 6.7441-6.4062-6.7246-6.7266z"></path>
</svg>
</a>
</div>
<% end %>
</nav>
11 changes: 11 additions & 0 deletions app/views/kaminari/_paginator.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<% anchor = anchor.presence || "" %>

<% if total_pages > 1 %>
<% PaginationHelper.pagination_hash(current_page: current_page.to_i, total_pages:, path: request.url + anchor).tap do |hash| %>
<%= render "components/pagination", {
previous_href: hash[:previous_href],
next_href: hash[:next_href],
items: hash[:items],
} %>
<% end %>
<% end %>
1 change: 1 addition & 0 deletions app/views/root/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
</div>
<div class="govuk-grid-column-two-thirds">
<%= render :partial => "table" %>
<%= paginate @presenter.editions %>
</div>
24 changes: 22 additions & 2 deletions test/functional/root_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,30 @@ class RootControllerTest < ActionController::TestCase
should "ignore unrecognised filter states" do
FilteredEditionsPresenter
.expects(:new)
.with(states_filter: %w[draft], assigned_to_filter: anything, format_filter: anything, title_filter: anything)
.returns(stub(editions: [], available_users: []))
.with(has_entry(:states_filter, %w[draft]))
.returns(stub(editions: Kaminari.paginate_array([]).page(1), available_users: []))

get :index, params: { states_filter: %w[draft not_a_real_state] }
end

should "show the first page of publications when no page is specified" do
FactoryBot.create_list(:guide_edition, FilteredEditionsPresenter::ITEMS_PER_PAGE + 1)

get :index

assert_response :ok
assert_select "p.publications-table__heading", "#{FilteredEditionsPresenter::ITEMS_PER_PAGE + 1} document(s)"
assert_select ".govuk-table__row .title", FilteredEditionsPresenter::ITEMS_PER_PAGE
end

should "show the specified page of publications" do
FactoryBot.create_list(:guide_edition, FilteredEditionsPresenter::ITEMS_PER_PAGE + 1)

get :index, params: { page: 2 }

assert_response :ok
assert_select "p.publications-table__heading", "#{FilteredEditionsPresenter::ITEMS_PER_PAGE + 1} document(s)"
assert_select ".govuk-table__row .title", 1
end
end
end
3 changes: 3 additions & 0 deletions test/integration/delete_edition_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class DeleteEditionTest < LegacyIntegrationTest
setup_users
stub_linkables
stub_holidays_used_by_fact_check

test_strategy = Flipflop::FeatureSet.current.test!
test_strategy.switch!(:design_system_publications_filter, false)
end

teardown do
Expand Down
3 changes: 3 additions & 0 deletions test/integration/edition_scheduled_publishing_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ class EditionScheduledPublishingTest < LegacyJavascriptIntegrationTest
stub_holidays_used_by_fact_check
# queue up the edition, don't perform inline
Sidekiq::Testing.fake!

test_strategy = Flipflop::FeatureSet.current.test!
test_strategy.switch!(:design_system_publications_filter, false)
end

teardown do
Expand Down
3 changes: 3 additions & 0 deletions test/integration/edition_workflow_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class EditionWorkflowTest < LegacyJavascriptIntegrationTest

@guide = FactoryBot.create(:guide_edition)
login_as "Alice"

test_strategy = Flipflop::FeatureSet.current.test!
test_strategy.switch!(:design_system_publications_filter, false)
end

teardown do
Expand Down
3 changes: 3 additions & 0 deletions test/integration/mark_edition_in_beta_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ class MarkEditionInBetaTest < LegacyJavascriptIntegrationTest
setup_users
stub_linkables
stub_holidays_used_by_fact_check

test_strategy = Flipflop::FeatureSet.current.test!
test_strategy.switch!(:design_system_publications_filter, false)
end

with_and_without_javascript do
Expand Down
28 changes: 28 additions & 0 deletions test/integration/root_overview_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require_relative "../integration_test_helper"

class RootOverviewTest < IntegrationTest
setup do
test_strategy = Flipflop::FeatureSet.current.test!
test_strategy.switch!(:design_system_publications_filter, true)
end

should "be able to view different pages of results" do
FactoryBot.create(:user, :govuk_editor, name: "Alice", uid: "alice")
FactoryBot.create(:guide_edition, title: "Guides and Gals")
FactoryBot.create_list(:guide_edition, FilteredEditionsPresenter::ITEMS_PER_PAGE)

visit "/"
assert_content("21 document(s)")
assert_no_content("Guides and Gals")

click_on "Next"
assert_content("21 document(s)")
assert_content("Guides and Gals")

click_on "Prev"
assert_content("21 document(s)")
assert_no_content("Guides and Gals")
end
end
Loading

0 comments on commit ef3a72c

Please sign in to comment.