From a814f65516d0388cdf2b4afb80bfc1720857262d Mon Sep 17 00:00:00 2001 From: davidtrussler Date: Mon, 29 Jul 2024 16:56:30 +0100 Subject: [PATCH] Add UI for Publications page filter section - Add stylesheet for Publications page - Add publishing components to filter form - Add new tests and update existing tests --- app/assets/stylesheets/application.scss | 4 +- ...lications-table.scss => publications.scss} | 14 ++++ app/controllers/root_controller.rb | 6 +- app/helpers/editions_helper.rb | 9 -- app/presenters/filtered_editions_presenter.rb | 83 +++++++++++++++++-- app/views/root/_filter.html.erb | 48 +++++++++++ app/views/root/index.html.erb | 18 +--- test/functional/root_controller_test.rb | 13 ++- .../filtered_editions_presenter_test.rb | 57 ++++++++++++- 9 files changed, 211 insertions(+), 41 deletions(-) rename app/assets/stylesheets/{publications-table.scss => publications.scss} (89%) create mode 100644 app/views/root/_filter.html.erb diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index a6ef809c1..ff04bfa48 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -4,6 +4,7 @@ $govuk-page-width: 1140px; @import 'govuk_publishing_components/govuk_frontend_support'; @import 'govuk_publishing_components/component_support'; @import 'govuk_publishing_components/components/button'; +@import 'govuk_publishing_components/components/checkboxes'; @import 'govuk_publishing_components/components/date-input'; @import 'govuk_publishing_components/components/document-list'; @import 'govuk_publishing_components/components/error-alert'; @@ -22,6 +23,7 @@ $govuk-page-width: 1140px; @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/select'; @import 'govuk_publishing_components/components/skip-link'; @import 'govuk_publishing_components/components/success-alert'; @import 'govuk_publishing_components/components/summary-list'; @@ -29,6 +31,6 @@ $govuk-page-width: 1140px; @import 'govuk_publishing_components/components/textarea'; @import 'govuk_publishing_components/components/title'; @import "downtimes"; +@import "publications"; @import "summary-card"; @import "popular_links"; -@import "publications-table"; diff --git a/app/assets/stylesheets/publications-table.scss b/app/assets/stylesheets/publications.scss similarity index 89% rename from app/assets/stylesheets/publications-table.scss rename to app/assets/stylesheets/publications.scss index cb5ebb689..8a069114d 100644 --- a/app/assets/stylesheets/publications-table.scss +++ b/app/assets/stylesheets/publications.scss @@ -1,5 +1,19 @@ $width: 40; +.publications-filter { + background: govuk-colour("light-grey"); + + .gem-c-select { + label { + font-weight: 700; + } + } + + .govuk-checkboxes__label::before { + background: govuk-colour("white"); + } +} + .publications-table { th:first-child { width: calc($width * 1%); diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 156b65c86..18afffd96 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -20,12 +20,12 @@ def index states_filter_params = filter_params_hash[:states_filter] sanitised_states_filter_params = states_filter_params&.select { |fp| PERMITTED_FILTER_STATES.include?(fp) } assignee_filter = filter_params_hash[:assignee_filter] - format_filter = filter_params_hash[:format_filter] + content_type_filter = filter_params_hash[:content_type_filter] title_filter = filter_params_hash[:title_filter] @presenter = FilteredEditionsPresenter.new( states_filter: sanitised_states_filter_params, assigned_to_filter: assignee_filter, - format_filter:, + content_type_filter:, title_filter:, page: filter_params_hash[:page], ) @@ -34,6 +34,6 @@ def index private def filter_params - params.permit(:page, :assignee_filter, :format_filter, :title_filter, states_filter: []) + params.permit(:page, :assignee_filter, :content_type_filter, :title_filter, states_filter: []) end end diff --git a/app/helpers/editions_helper.rb b/app/helpers/editions_helper.rb index 314561349..0fe663a63 100644 --- a/app/helpers/editions_helper.rb +++ b/app/helpers/editions_helper.rb @@ -32,13 +32,4 @@ def legacy_format_filter_selection_options [displayed_format_name, format_name] end end - - def format_filter_selection_options - [%w[All all]] + - Artefact::FORMATS_BY_DEFAULT_OWNING_APP["publisher"].map do |format_name| - displayed_format_name = format_name.humanize - displayed_format_name += " (Retired)" if Artefact::RETIRED_FORMATS.include?(format_name) - [displayed_format_name, format_name] - end - end end diff --git a/app/presenters/filtered_editions_presenter.rb b/app/presenters/filtered_editions_presenter.rb index b0dc99245..92ed35da2 100644 --- a/app/presenters/filtered_editions_presenter.rb +++ b/app/presenters/filtered_editions_presenter.rb @@ -3,20 +3,66 @@ class FilteredEditionsPresenter ITEMS_PER_PAGE = 20 - def initialize(states_filter: [], assigned_to_filter: nil, format_filter: nil, title_filter: nil, page: nil) + def initialize(states_filter: [], assigned_to_filter: nil, content_type_filter: nil, title_filter: nil, page: nil) @states_filter = states_filter || [] @assigned_to_filter = assigned_to_filter - @format_filter = format_filter + @content_type_filter = content_type_filter @title_filter = title_filter @page = page end + def title + @title_filter + end + + def content_types + types = [] + + content_type_filter_selection_options.map do |content_type| + types << if content_type[1] == @content_type_filter + { text: content_type[0], value: content_type[1], selected: "true" } + else + { text: content_type[0], value: content_type[1] } + end + end + + types + end + + def edition_states + states = [] + + state_names.map do |scope, status_label| + states << if @states_filter.include? scope.to_s + { label: status_label, value: scope, checked: "true" } + else + { label: status_label, value: scope } + end + end + + states + end + def available_users User.enabled.alphabetized end + def assignees + users = [{ text: "All assignees", value: "" }] + + available_users.map do |user| + users << if user.id.to_s == @assigned_to_filter + { text: user.name, value: user.id, selected: "true" } + else + { text: user.name, value: user.id } + end + end + + users + end + def editions - result = editions_by_format + result = editions_by_content_type result = apply_states_filter(result) result = apply_assigned_to_filter(result) result = apply_title_filter(result) @@ -26,10 +72,33 @@ def editions private - def editions_by_format - return Edition.all unless format_filter && format_filter != "all" + def state_names + { + draft: "Drafts", + in_review: "In review", + amends_needed: "Amends needed", + fact_check: "Out for fact check", + fact_check_received: "Fact check received", + ready: "Ready", + scheduled_for_publishing: "Scheduled", + published: "Published", + archived: "Archived", + } + end + + def content_type_filter_selection_options + [%w[All all]] + + Artefact::FORMATS_BY_DEFAULT_OWNING_APP["publisher"].map do |format_name| + displayed_format_name = format_name.humanize + displayed_format_name += " (Retired)" if Artefact::RETIRED_FORMATS.include?(format_name) + [displayed_format_name, format_name] + end + end + + def editions_by_content_type + return Edition.all unless content_type_filter && content_type_filter != "all" - Edition.where(_type: "#{format_filter.camelcase}Edition") + Edition.where(_type: "#{content_type_filter.camelcase}Edition") end def apply_states_filter(editions) @@ -60,5 +129,5 @@ def apply_title_filter(editions) editions.title_contains(title_filter) end - attr_reader :states_filter, :assigned_to_filter, :format_filter, :title_filter, :page + attr_reader :states_filter, :assigned_to_filter, :content_type_filter, :title_filter, :page end diff --git a/app/views/root/_filter.html.erb b/app/views/root/_filter.html.erb new file mode 100644 index 000000000..00baa563e --- /dev/null +++ b/app/views/root/_filter.html.erb @@ -0,0 +1,48 @@ +
+ <%= form_with url: root_path, method: :get do |form| %> + <%= render "govuk_publishing_components/components/heading", { + text: "Filter by", + margin_bottom: 4, + } %> + + <%= render "govuk_publishing_components/components/input", { + label: { + text: "Title", + bold: true, + }, + name: "title_filter", + id: "title_filter", + value: @presenter.title, + } %> + + <%= render "govuk_publishing_components/components/select", { + id: "assignee_filter", + full_width: true, + label: "Assigned to", + options: @presenter.assignees, + } %> + + <%= render "govuk_publishing_components/components/select", { + id: "content_type_filter", + label: "Content type", + full_width: true, + options: @presenter.content_types, + } %> + + <%= render "govuk_publishing_components/components/checkboxes", { + name: "states_filter[]", + heading: "Status", + heading_size: "s", + items: @presenter.edition_states, + } %> + + <%= render "govuk_publishing_components/components/button", { + text: "Update filter", + margin_bottom: 4, + } %> + +

+ <%= link_to "Reset all fields", root_path, class: "govuk-link" %> +

+ <% end %> +
diff --git a/app/views/root/index.html.erb b/app/views/root/index.html.erb index f14aa23a7..52295306b 100644 --- a/app/views/root/index.html.erb +++ b/app/views/root/index.html.erb @@ -2,23 +2,9 @@ <% content_for :title, "Publications" %>
- <%= form_with url: root_path, method: :get do |form| %> - <%= form.label :title_filter, "Title" %> - <%= text_field_tag :title_filter %> - <%= form.label :format_filter, "Format" %> - <%= select_tag :format_filter, - options_for_select(format_filter_selection_options) %> - <%= form.label :assignee_filter, "Assignee" %> - <%= select_tag :assignee_filter, - options_for_select([%w[Nobody nobody]]) << - options_from_collection_for_select(@presenter.available_users, "id", "name") %> - <%= form.label :states_filter_draft, "Draft" %> - <%= check_box_tag "states_filter[]", "draft", false, { id: "states_filter_draft" } %> - <%= form.label :states_filter_published, "Published" %> - <%= check_box_tag "states_filter[]", "published", false, :id => "states_filter_published" %> - <%= form.submit %> - <% end %> + <%= render :partial => "filter" %>
+
<%= render :partial => "table" %> <%= paginate @presenter.editions %> diff --git a/test/functional/root_controller_test.rb b/test/functional/root_controller_test.rb index 52a0a8eb4..3e5d5e858 100644 --- a/test/functional/root_controller_test.rb +++ b/test/functional/root_controller_test.rb @@ -33,11 +33,11 @@ class RootControllerTest < ActionController::TestCase assert_select "p.publications-table__heading", "1 document(s)" end - should "filter publications by format" do + should "filter publications by content type" do FactoryBot.create(:guide_edition) FactoryBot.create(:completed_transaction_edition) - get :index, params: { format_filter: "guide" } + get :index, params: { content_type_filter: "guide" } assert_response :ok assert_select "p.publications-table__heading", "1 document(s)" @@ -57,7 +57,14 @@ class RootControllerTest < ActionController::TestCase FilteredEditionsPresenter .expects(:new) .with(has_entry(:states_filter, %w[draft])) - .returns(stub(editions: Kaminari.paginate_array([]).page(1), available_users: [])) + .returns(stub( + editions: Kaminari.paginate_array([]).page(1), + available_users: [], + title: "", + assignees: [], + content_types: [], + edition_states: [], + )) get :index, params: { states_filter: %w[draft not_a_real_state] } end diff --git a/test/unit/presenters/filtered_editions_presenter_test.rb b/test/unit/presenters/filtered_editions_presenter_test.rb index f68d658fc..679f86f16 100644 --- a/test/unit/presenters/filtered_editions_presenter_test.rb +++ b/test/unit/presenters/filtered_editions_presenter_test.rb @@ -3,6 +3,59 @@ require "test_helper" class FilteredEditionsPresenterTest < ActiveSupport::TestCase + context "#content_types" do + should "return content types with none selected when no content type filter has been specified" do + content_types = FilteredEditionsPresenter.new.content_types + + assert_equal(13, content_types.count) + assert_includes(content_types, { text: "All", value: "all" }) + assert_includes(content_types, { text: "Answer", value: "answer" }) + assert_includes(content_types, { text: "Campaign (Retired)", value: "campaign" }) + assert_includes(content_types, { text: "Completed transaction", value: "completed_transaction" }) + assert_includes(content_types, { text: "Guide", value: "guide" }) + assert_includes(content_types, { text: "Help page", value: "help_page" }) + assert_includes(content_types, { text: "Licence (Retired)", value: "licence" }) + assert_includes(content_types, { text: "Local transaction", value: "local_transaction" }) + assert_includes(content_types, { text: "Place", value: "place" }) + assert_includes(content_types, { text: "Programme (Retired)", value: "programme" }) + assert_includes(content_types, { text: "Simple smart answer", value: "simple_smart_answer" }) + assert_includes(content_types, { text: "Transaction", value: "transaction" }) + assert_includes(content_types, { text: "Video (Retired)", value: "video" }) + end + + should "mark the relevant content type as selected when a content type filter has been specified" do + content_types = FilteredEditionsPresenter.new(content_type_filter: "answer").content_types + + assert_includes(content_types, { text: "Answer", value: "answer", selected: "true" }) + assert_includes(content_types, { text: "Place", value: "place" }) # Not selected + end + end + + context "#edition_states" do + should "return states with none checked when no state filter has been specified" do + states = FilteredEditionsPresenter.new.edition_states + + assert_equal(9, states.count) + assert_includes(states, { label: "Drafts", value: :draft }) + assert_includes(states, { label: "In review", value: :in_review }) + assert_includes(states, { label: "Amends needed", value: :amends_needed }) + assert_includes(states, { label: "Out for fact check", value: :fact_check }) + assert_includes(states, { label: "Fact check received", value: :fact_check_received }) + assert_includes(states, { label: "Ready", value: :ready }) + assert_includes(states, { label: "Scheduled", value: :scheduled_for_publishing }) + assert_includes(states, { label: "Published", value: :published }) + assert_includes(states, { label: "Archived", value: :archived }) + end + + should "mark the relevant states as checked when a state filter has been specified" do + states = FilteredEditionsPresenter.new(states_filter: %w[in_review ready]).edition_states + + assert_includes(states, { label: "In review", value: :in_review, checked: "true" }) + assert_includes(states, { label: "Ready", value: :ready, checked: "true" }) + assert_includes(states, { label: "Published", value: :published }) # Not checked + end + end + context "#editions" do should "return all editions when no filters are specified" do draft_guide = FactoryBot.create(:guide_edition, state: "draft") @@ -60,7 +113,7 @@ class FilteredEditionsPresenterTest < ActiveSupport::TestCase guide = FactoryBot.create(:guide_edition) FactoryBot.create(:completed_transaction_edition) - filtered_editions = FilteredEditionsPresenter.new(format_filter: "guide").editions + filtered_editions = FilteredEditionsPresenter.new(content_type_filter: "guide").editions assert_equal([guide], filtered_editions.to_a) end @@ -69,7 +122,7 @@ class FilteredEditionsPresenterTest < ActiveSupport::TestCase FactoryBot.create(:guide_edition) FactoryBot.create(:completed_transaction_edition) - filtered_editions = FilteredEditionsPresenter.new(format_filter: "all").editions + filtered_editions = FilteredEditionsPresenter.new(content_type_filter: "all").editions assert_equal(2, filtered_editions.count) end