From 6de4acbebedff08d6bfbd3fcf0d1acf5c6e88779 Mon Sep 17 00:00:00 2001 From: CatalinVoineag <11318084+CatalinVoineag@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:18:51 +0100 Subject: [PATCH] Support claims index page filters We need to allow our support users to filter the claims. Two filters, School and Provider filters have 2 search fields where you can search by school name or provider to get the filter you want. This is done by javascript in a stimulus controller. The other 2 filters, Submitted after and Submitted before filters are date filters and use the govuk_date_field form helper. You cannot set the value of this helper as it's sending 3 params not just 1. So to persist the value of these filters we need a form object. Claims::Support::Claims::FilterForm is a form object that is meant to just house the params and all the logic that these filter need. All the params of the filters are in the url. --- app/assets/images/icon-magnifying-glass.svg | 3 + app/assets/stylesheets/filter-form.scss | 24 +- app/components/claim/card_component.html.erb | 2 +- .../claims/support/claims_controller.rb | 27 +- .../claims/support/claims/filter_form.rb | 111 ++++++++ ...claims_support_filter_search_controller.js | 48 ++++ app/javascript/controllers/index.js | 3 + app/queries/claims/claims_query.rb | 15 + .../claims/support/claims/_filter.html.erb | 159 +++++++++++ .../claims/support/claims/index.html.erb | 42 ++- config/locales/en.yml | 4 + config/locales/en/claims/support/claims.yml | 11 + spec/components/claim/card_component_spec.rb | 2 +- .../claims/support/claims/filter_form_spec.rb | 260 ++++++++++++++++++ spec/queries/claims/claims_query_spec.rb | 22 ++ .../claims/support/claims/view_claims_spec.rb | 137 ++++++++- 16 files changed, 843 insertions(+), 27 deletions(-) create mode 100644 app/assets/images/icon-magnifying-glass.svg create mode 100644 app/forms/claims/support/claims/filter_form.rb create mode 100644 app/javascript/controllers/claims_support_filter_search_controller.js create mode 100644 app/views/claims/support/claims/_filter.html.erb create mode 100644 spec/forms/claims/support/claims/filter_form_spec.rb diff --git a/app/assets/images/icon-magnifying-glass.svg b/app/assets/images/icon-magnifying-glass.svg new file mode 100644 index 000000000..997be4315 --- /dev/null +++ b/app/assets/images/icon-magnifying-glass.svg @@ -0,0 +1,3 @@ + diff --git a/app/assets/stylesheets/filter-form.scss b/app/assets/stylesheets/filter-form.scss index 2714e5193..971deb743 100644 --- a/app/assets/stylesheets/filter-form.scss +++ b/app/assets/stylesheets/filter-form.scss @@ -238,7 +238,6 @@ &:hover:after { background-image: url("icon-tag-remove-cross-white.svg"); } - } .app-filter__options { @@ -258,3 +257,26 @@ } } } + +.app-filter__option .govuk-checkboxes { + position: relative; + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; +} + +.app-filter__option input[type=search] { + background: url("icon-magnifying-glass.svg") no-repeat; + background-color: govuk-colour("white"); + padding-left: govuk-spacing(6); +} + +.app-filter__option:not(:last-of-type) { + border-bottom: 1px solid $govuk-border-colour; + margin-bottom: govuk-spacing(4); + + div:last-of-type { + margin-bottom: govuk-spacing(1); + } +} + diff --git a/app/components/claim/card_component.html.erb b/app/components/claim/card_component.html.erb index df9123c50..a86c93e55 100644 --- a/app/components/claim/card_component.html.erb +++ b/app/components/claim/card_component.html.erb @@ -13,7 +13,7 @@
+ <%= govuk_link_to( + t("clear_filters"), + filter_form.clear_filters_path, + no_visited_state: true, + ) %> +
++ <%= govuk_link_to t(".clear"), filter_form.clear_search_path, no_visited_state: true %> +
+ <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 135180d32..6dc6d74ba 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -11,6 +11,10 @@ en: cancel: Cancel continue: Continue change: Change + filter: Filter + apply_filters: Apply filters + selected_filters: Selected filters + clear_filters: Clear filters date: formats: month: "%B" diff --git a/config/locales/en/claims/support/claims.yml b/config/locales/en/claims/support/claims.yml index e5cd73eb5..9e57dcff5 100644 --- a/config/locales/en/claims/support/claims.yml +++ b/config/locales/en/claims/support/claims.yml @@ -6,6 +6,17 @@ en: heading: Claims (%{count}) download_csv: Download claims search_label: Search by claim reference + school: School + accredited_provider: Accredited provider + submitted_after: Submitted after + submitted_after_hint: For example, 31 3 2024 + submitted_before: Submitted before + submitted_before_hint: For example, 31 7 2024 + school_ids: School + provider_ids: Accredited prvoider + provider: Accredited prvoider + submit: Search + clear: Clear search show: heading: Claim status: Status diff --git a/spec/components/claim/card_component_spec.rb b/spec/components/claim/card_component_spec.rb index 816b7ed87..02eb6fda2 100644 --- a/spec/components/claim/card_component_spec.rb +++ b/spec/components/claim/card_component_spec.rb @@ -6,7 +6,7 @@ subject(:component) { described_class.new(claim:) } let(:claim) do - create(:claim, :submitted, created_at: "2024/04/08", school:) do |claim| + create(:claim, :submitted, submitted_at: "2024/04/08", school:) do |claim| claim.mentor_trainings << create(:mentor_training, hours_completed: 20) end end diff --git a/spec/forms/claims/support/claims/filter_form_spec.rb b/spec/forms/claims/support/claims/filter_form_spec.rb new file mode 100644 index 000000000..06690f879 --- /dev/null +++ b/spec/forms/claims/support/claims/filter_form_spec.rb @@ -0,0 +1,260 @@ +require "rails_helper" + +describe Claims::Support::Claims::FilterForm, type: :model do + include Rails.application.routes.url_helpers + + describe "#filters_selected?" do + it "returns true if school_ids present" do + params = { school_ids: %w[school_id] } + form = described_class.new(params) + + expect(form.filters_selected?).to eq(true) + end + + it "returns true if provider_ids present" do + params = { provider_ids: %w[provider_id] } + form = described_class.new(params) + + expect(form.filters_selected?).to eq(true) + end + + it "returns true if submitted_after is present" do + params = { + "submitted_after(1i)" => "2024", + "submitted_after(2i)" => "1", + "submitted_after(3i)" => "2", + } + form = described_class.new(params) + + expect(form.filters_selected?).to eq(true) + end + + it "returns true if submitted_before is present" do + params = { + "submitted_before(1i)" => "2024", + "submitted_before(2i)" => "1", + "submitted_before(3i)" => "2", + } + form = described_class.new(params) + + expect(form.filters_selected?).to eq(true) + end + + context "when filters are not present" do + it "returns false" do + form = described_class.new({}) + + expect(form.filters_selected?).to eq(false) + end + end + end + + describe "#index_path_without_filter" do + it "returns the support claims index path without a filter" do + params = { school_ids: %w[school_id] } + call = described_class.new(params).index_path_without_filter(filter: "school_ids", value: "school_id") + + expect(call).to eq( + claims_support_claims_path( + params: { claims_support_claims_filter_form: { school_ids: [], provider_ids: [] } }, + ), + ) + end + end + + describe "#index_path_without_submitted_dates" do + context "with submitted_after" do + it "returns the support claims index path without submitted dates" do + params = { + "submitted_after(1i)" => "2024", + "submitted_after(2i)" => "1", + "submitted_after(3i)" => "2", + } + call = described_class.new(params).index_path_without_submitted_dates("submitted_after") + + expect(call).to eq( + claims_support_claims_path( + params: { claims_support_claims_filter_form: { school_ids: [], provider_ids: [] } }, + ), + ) + end + end + + context "with submitted_before" do + it "returns the support claims index path without submitted dates" do + params = { + "submitted_before(1i)" => "2024", + "submitted_before(2i)" => "1", + "submitted_before(3i)" => "2", + } + call = described_class.new(params).index_path_without_submitted_dates("submitted_before") + + expect(call).to eq( + claims_support_claims_path( + params: { claims_support_claims_filter_form: { school_ids: [], provider_ids: [] } }, + ), + ) + end + end + end + + describe "#clear_filters_path" do + it "returns the support claims index path without filters but with search" do + params = { school_ids: %w[school_id], search: "search" } + call = described_class.new(params).clear_filters_path + + expect(call).to eq( + claims_support_claims_path( + params: { claims_support_claims_filter_form: { search: "search" } }, + ), + ) + end + + context "when search param is not present" do + it "returns the support claims index path without filters and search" do + params = { school_ids: %w[school_id] } + call = described_class.new(params).clear_filters_path + + expect(call).to eq(claims_support_claims_path) + end + end + end + + describe "#clear_search_path" do + it "returns the support claims index path without search but with filters" do + params = { school_ids: %w[school_id], search: "search" } + call = described_class.new(params).clear_search_path + + expect(call).to eq( + claims_support_claims_path( + params: { claims_support_claims_filter_form: { school_ids: %w[school_id] } }, + ), + ) + end + + context "when filters are not present" do + it "returns the support claims index path without search and without filters" do + params = { search: "search" } + call = described_class.new(params).clear_search_path + + expect(call).to eq(claims_support_claims_path) + end + end + end + + describe "#schools" do + it "returns a collection of claims schools based on school_ids param" do + school = create(:claims_school) + params = { school_ids: [school.id] } + call = described_class.new(params).schools + + expect(call).to eq([school]) + end + + context "when school_ids is empty" do + it "returns empty array" do + params = { school_ids: [] } + call = described_class.new(params).schools + + expect(call).to eq([]) + end + end + end + + describe "#providers" do + it "returns a collection of claims providers based on provider_ids param" do + provider = create(:claims_provider) + params = { provider_ids: [provider.id] } + call = described_class.new(params).providers + + expect(call).to eq([provider]) + end + + context "when provider_ids is empty" do + it "returns empty array" do + params = { provider_ids: [] } + call = described_class.new(params).providers + + expect(call).to eq([]) + end + end + end + + describe "#query_params" do + it "returns the query params meant for searching the database" do + params = { + search: "claim_reference", + search_school: "school_name", + search_provider: "provider", + "submitted_after(1i)" => "2024", + "submitted_after(2i)" => "1", + "submitted_after(3i)" => "2", + "submitted_before(1i)" => "2023", + "submitted_before(2i)" => "1", + "submitted_before(3i)" => "2", + school_ids: %w[school_id], + provider_ids: %w[provider_id], + } + + call = described_class.new(params).query_params + + expect(call).to eq( + provider_ids: %w[provider_id], + school_ids: %w[school_id], + search: "claim_reference", + search_provider: "provider", + search_school: "school_name", + submitted_after: Date.new(2024, 1, 2), + submitted_before: Date.new(2023, 1, 2), + ) + end + end + + describe "#submitted_after" do + it "the Submitted after date in Date format" do + params = { + "submitted_after(1i)" => "2024", + "submitted_after(2i)" => "1", + "submitted_after(3i)" => "2", + } + call = described_class.new(params).submitted_after + + expect(call).to eq(Date.new(2024, 1, 2)) + end + + context "when submitted after params are not valid" do + it "the Submitted after date in Date format" do + params = { + "submitted_after(1i)" => "invalid", + } + call = described_class.new(params).submitted_after + + expect(call).to eq(nil) + end + end + end + + describe "#submitted_before" do + it "the Submitted before date in Date format" do + params = { + "submitted_before(1i)" => "2024", + "submitted_before(2i)" => "1", + "submitted_before(3i)" => "2", + } + call = described_class.new(params).submitted_before + + expect(call).to eq(Date.new(2024, 1, 2)) + end + + context "when submitted before params are not valid" do + it "the Submitted before date in Date format" do + params = { + "submitted_before(1i)" => "invalid", + } + call = described_class.new(params).submitted_before + + expect(call).to eq(nil) + end + end + end +end diff --git a/spec/queries/claims/claims_query_spec.rb b/spec/queries/claims/claims_query_spec.rb index aabdb10ed..419472790 100644 --- a/spec/queries/claims/claims_query_spec.rb +++ b/spec/queries/claims/claims_query_spec.rb @@ -48,5 +48,27 @@ expect(claims_query).to match_array([claim_belonging_to_filtered_provider]) end end + + context "when given submitted_after" do + let(:params) { { submitted_after: 2.days.ago } } + + it "filters the results by provided provider ids" do + expected_claim = create(:claim, :submitted, submitted_at: 1.day.ago) + _unexpected_claim = create(:claim, :submitted, submitted_at: 3.days.ago) + + expect(claims_query).to match_array([expected_claim]) + end + end + + context "when given submitted_before" do + let(:params) { { submitted_before: 2.days.ago } } + + it "filters the results by provided provider ids" do + expected_claim = create(:claim, :submitted, submitted_at: 3.days.ago) + _unexpected_claim = create(:claim, :submitted, submitted_at: 1.day.ago) + + expect(claims_query).to match_array([expected_claim]) + end + end end end diff --git a/spec/system/claims/support/claims/view_claims_spec.rb b/spec/system/claims/support/claims/view_claims_spec.rb index 3a754685f..e23bcf0d6 100644 --- a/spec/system/claims/support/claims/view_claims_spec.rb +++ b/spec/system/claims/support/claims/view_claims_spec.rb @@ -1,9 +1,18 @@ require "rails_helper" -RSpec.describe "View claims", type: :system, service: :claims do +RSpec.describe "View claims", type: :system, service: :claims, js: true do let!(:support_user) { create(:claims_support_user) } - let!(:claim_2) { create(:claim, :submitted) } - let!(:claim_1) { create(:claim, :draft) } + let!(:school_1) { create(:claims_school) } + let!(:school_2) { create(:claims_school) } + let!(:provider_1) { create(:claims_provider, :best_practice_network) } + let!(:provider_2) { create(:claims_provider, :niot) } + let!(:claim_1) { create(:claim, :draft, school: school_1) } + let!(:claim_2) { create(:claim, :submitted, school: school_1, provider: provider_1, submitted_at: Date.new(2024, 4, 7), created_at: Date.new(2024, 4, 7)) } + let!(:claim_3) { create(:claim, :submitted, school: school_1, provider: provider_1, submitted_at: Date.new(2024, 4, 6), created_at: Date.new(2024, 4, 6)) } + let!(:claim_4) { create(:claim, :submitted, school: school_1, provider: provider_1, submitted_at: Date.new(2024, 4, 5), created_at: Date.new(2024, 4, 5)) } + let!(:claim_5) { create(:claim, :submitted, school: school_1, provider: provider_1, submitted_at: Date.new(2024, 4, 4), created_at: Date.new(2024, 4, 4)) } + let!(:claim_6) { create(:claim, :submitted, school: school_1, provider: provider_2, submitted_at: Date.new(2024, 4, 3), created_at: Date.new(2024, 4, 3)) } + let!(:claim_7) { create(:claim, :submitted, school: school_2, provider: provider_2, submitted_at: Date.new(2024, 4, 2), created_at: Date.new(2024, 4, 2)) } before do user_exists_in_dfe_sign_in(user: support_user) @@ -12,8 +21,41 @@ scenario "Support user visits the claims index page" do when_i_visit_claim_index_page - then_i_see_a_list_of_submitted_claims + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5, claim_6, claim_7]) and_i_see_no_draft_claims + when_i_check_school_filter(school_1) + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5, claim_6]) + when_i_check_provider_filter(provider_1) + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5]) + when_i_set_submitted_after(5, 4, 2024) + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4]) + when_i_set_submitted_before(6, 4, 2024) + then_i_see_a_list_of_submitted_claims([claim_3, claim_4]) + when_i_search_for_claim_reference(claim_3.reference) + then_i_see_a_list_of_submitted_claims([claim_3]) + when_i_remove_my_search + then_i_see_a_list_of_submitted_claims([claim_3, claim_4]) + when_i_remove_the_filter("06/04/2024") + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4]) + when_i_remove_the_filter("05/04/2024") + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5]) + when_i_remove_the_filter(provider_1.name) + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5, claim_6]) + when_i_remove_the_filter(school_1.name) + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5, claim_6, claim_7]) + end + + scenario "Support user uses the js filter search" do + when_i_visit_claim_index_page + then_i_see_a_list_of_submitted_claims([claim_2, claim_3, claim_4, claim_5, claim_6, claim_7]) + when_i_search_the_school_filer_with(school_2.name) + then_i_see_only_my_filter_school_as_an_option + when_i_check_school_filter(school_2) + then_i_see_a_list_of_submitted_claims([claim_7]) + then_i_see_my_search_school_filter_populated(school_2.name) + when_i_search_the_provider_filer_with(provider_2.name) + when_i_check_provider_filter(provider_2) + then_i_see_a_list_of_submitted_claims([claim_7]) end private @@ -27,18 +69,89 @@ def when_i_visit_claim_index_page click_on("Claims") end - def then_i_see_a_list_of_submitted_claims - within(".claim-card:nth-child(1)") do - expect(page).to have_content(claim_2.school.name) - expect(page).to have_content(claim_2.reference) - expect(page).to have_content("Submitted") - expect(page).to have_content(claim_2.provider.name) - expect(page).to have_content(I18n.l(claim_2.created_at.to_date, format: :short)) - expect(page).to have_content(claim_2.amount.format(no_cents_if_whole: true)) + def then_i_see_a_list_of_submitted_claims(claims) + claims.each_with_index do |claim, index| + within(".claim-card:nth-child(#{index + 1})") do + expect(page).to have_content(claim.school.name) + expect(page).to have_content(claim.reference) + expect(page).to have_content("Submitted") + expect(page).to have_content(claim.provider.name) + expect(page).to have_content(I18n.l(claim.submitted_at.to_date, format: :short)) + expect(page).to have_content(claim.amount.format(no_cents_if_whole: true)) + end end + expect(claims.count).to eq(page.find_all(".claim-card").count) end def and_i_see_no_draft_claims expect(page).not_to have_content(claim_1.reference) end + + def when_i_check_school_filter(school) + page.find("#claims-support-claims-filter-form-school-ids-#{school.id}-field", visible: :all).check + click_on("Apply filters") + end + + def when_i_check_provider_filter(provider) + page.find("#claims-support-claims-filter-form-provider-ids-#{provider.id}-field", visible: :all).check + click_on("Apply filters") + end + + def when_i_set_submitted_after(day, month, year) + within_fieldset("Submitted after") do + fill_in("Day", with: day) + fill_in("Month", with: month) + fill_in("Year", with: year) + end + click_on("Apply filters") + end + + def when_i_set_submitted_before(day, month, year) + within_fieldset("Submitted before") do + fill_in("Day", with: day) + fill_in("Month", with: month) + fill_in("Year", with: year) + end + click_on("Apply filters") + end + + def when_i_search_for_claim_reference(reference) + fill_in("Search by claim reference", with: reference) + click_on("Search") + end + + def when_i_remove_my_search + fill_in("Search by claim reference", with: nil) + click_on("Search") + end + + def when_i_remove_the_filter(filter) + within(".app-filter-layout__selected") do + click_link(filter) + end + end + + def when_i_search_the_school_filer_with(school_name) + fill_in("School", with: school_name) + end + + def when_i_search_the_provider_filer_with(provider_name) + fill_in("Accredited prvoider", with: provider_name) + end + + def then_i_see_only_my_filter_school_as_an_option + my_selection = page.find("#claims-support-claims-filter-form-school-ids-#{school_2.id}-field", visible: :all) + expect(my_selection.present?).to eq(true) + + other_school_option = page.find_all("#claims-support-claims-filter-form-school-ids-#{school_1.id}-field") + expect(other_school_option.blank?).to eq(true) + end + + def then_i_see_my_search_school_filter_populated(school_name) + expect(page).to have_field("School", with: school_name) + end + + def then_i_see_my_search_provider_filter_populated(provider_name) + expect(page).to have_field("Accredited prvoider", with: provider_name) + end end