Browse Source

CLDC-2246 Add schemes status filters (#1796)

* Filter schemes

* Add status filter to schemes pages

* Filter schemes deactivating in more than 6 monts as active

* Always serialize session filters for schemes

* Remove ordering in search, fix tests

* Update incomplete scope
pull/1808/head
kosiakkatrina 1 year ago committed by GitHub
parent
commit
824451eca1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      app/controllers/organisations_controller.rb
  2. 13
      app/controllers/schemes_controller.rb
  3. 10
      app/helpers/filters_helper.rb
  4. 52
      app/models/scheme.rb
  5. 17
      app/services/filter_manager.rb
  6. 33
      app/views/organisations/schemes.html.erb
  7. 29
      app/views/schemes/_scheme_filters.html.erb
  8. 14
      app/views/schemes/index.html.erb
  9. 4
      spec/factories/scheme.rb
  10. 33
      spec/features/schemes_spec.rb
  11. 69
      spec/models/scheme_spec.rb
  12. 112
      spec/requests/schemes_controller_spec.rb
  13. 5
      spec/views/organisations/schemes.html.erb_spec.rb
  14. 5
      spec/views/schemes/index.html.erb_spec.rb

9
app/controllers/organisations_controller.rb

@ -6,9 +6,9 @@ class OrganisationsController < ApplicationController
before_action :find_resource, except: %i[index new create]
before_action :authenticate_scope!, except: [:index]
before_action :session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv]
before_action :session_filters, only: %i[users]
before_action :session_filters, only: %i[users schemes]
before_action -> { filter_manager.serialize_filters_to_session }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv]
before_action -> { filter_manager.serialize_filters_to_session }, only: %i[users]
before_action -> { filter_manager.serialize_filters_to_session }, only: %i[users schemes]
def index
redirect_to organisation_path(current_user.organisation) unless current_user.support?
@ -22,9 +22,10 @@ class OrganisationsController < ApplicationController
def schemes
all_schemes = Scheme.where(owning_organisation: [@organisation] + @organisation.parent_organisations).order_by_completion.order_by_service_name
@pagy, @schemes = pagy(filtered_collection(all_schemes, search_term))
@pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters))
@searched = search_term.presence
@total_count = all_schemes.size
@filter_type = "schemes"
end
def show
@ -209,6 +210,8 @@ private
"sales_logs"
elsif params[:action].include?("users")
"users"
elsif params[:action].include?("schemes")
"schemes"
end
end

13
app/controllers/schemes_controller.rb

@ -6,6 +6,8 @@ class SchemesController < ApplicationController
before_action :find_resource, except: %i[index create new]
before_action :redirect_if_scheme_confirmed, only: %i[primary_client_group confirm_secondary_client_group secondary_client_group support details]
before_action :authorize_user
before_action :session_filters, if: :current_user, only: %i[index]
before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index]
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
@ -13,9 +15,10 @@ class SchemesController < ApplicationController
redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support?
all_schemes = Scheme.order_by_completion.order_by_service_name
@pagy, @schemes = pagy(filtered_collection(all_schemes, search_term))
@pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters))
@searched = search_term.presence
@total_count = all_schemes.size
@filter_type = "schemes"
end
def show
@ -336,4 +339,12 @@ private
logs.update!(location: nil, scheme: nil, unresolved: true)
logs
end
def filter_manager
FilterManager.new(current_user:, session:, params:, filter_type: "schemes")
end
def session_filters
filter_manager.session_filters
end
end

10
app/helpers/filters_helper.rb

@ -46,6 +46,16 @@ module FiltersHelper
}.freeze
end
def scheme_status_filters
{
"incomplete" => "Incomplete",
"active" => "Active",
"deactivating_soon" => "Deactivating soon",
"reactivating_soon" => "Reactivating soon",
"deactivated" => "Deactivated",
}.freeze
end
def selected_option(filter, filter_type)
return false unless session[session_name_for(filter_type)]

52
app/models/scheme.rb

@ -17,8 +17,58 @@ class Scheme < ApplicationRecord
.or(filter_by_id(param)).distinct
}
scope :order_by_completion, -> { order("confirmed ASC NULLS FIRST") }
scope :order_by_completion, -> { order("schemes.confirmed ASC NULLS FIRST") }
scope :order_by_service_name, -> { order(service_name: :asc) }
scope :filter_by_status, lambda { |statuses, _user = nil|
filtered_records = all
scopes = []
statuses.each do |status|
status = status == "active" ? "active_status" : status
if respond_to?(status, true)
scopes << send(status)
end
end
if scopes.any?
filtered_records = filtered_records
.left_outer_joins(:scheme_deactivation_periods)
.order("scheme_deactivation_periods.created_at DESC")
.merge(scopes.reduce(&:or))
end
filtered_records
}
scope :incomplete, lambda {
where.not(confirmed: true)
.or(where.not(id: Location.select(:scheme_id).where(confirmed: true).distinct))
.where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id))
}
scope :deactivated, lambda {
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date <= ?", Time.zone.now)
}
scope :deactivating_soon, lambda {
merge(SchemeDeactivationPeriod.deactivations_without_reactivation)
.where("scheme_deactivation_periods.deactivation_date > ? AND scheme_deactivation_periods.deactivation_date < ? ", Time.zone.now, 6.months.from_now)
}
scope :reactivating_soon, lambda {
where.not("scheme_deactivation_periods.reactivation_date IS NULL")
.where("scheme_deactivation_periods.reactivation_date > ?", Time.zone.now)
}
scope :active_status, lambda {
where.not(id: joins(:scheme_deactivation_periods).reactivating_soon.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivated.pluck(:id))
.where.not(id: incomplete.pluck(:id))
.where.not(id: joins(:scheme_deactivation_periods).deactivating_soon.pluck(:id))
}
validate :validate_confirmed
validate :validate_owning_organisation

17
app/services/filter_manager.rb

@ -50,6 +50,17 @@ class FilterManager
users
end
def self.filter_schemes(schemes, search_term, filters, user)
schemes = filter_by_search(schemes, search_term)
filters.each do |category, values|
next if Array(values).reject(&:empty?).blank?
schemes = schemes.public_send("filter_by_#{category}", values, user)
end
schemes
end
def serialize_filters_to_session(specific_org: false)
session[session_name_for(filter_type)] = session_filters(specific_org:).to_json
end
@ -73,7 +84,7 @@ class FilterManager
new_filters["user"] = current_user.id.to_s if params["assigned_to"] == "you"
end
if @filter_type.include?("users") && params["status"].present?
if (@filter_type.include?("schemes") || @filter_type.include?("users")) && params["status"].present?
new_filters["status"] = params["status"]
end
@ -90,6 +101,10 @@ class FilterManager
FilterManager.filter_users(users, search_term, filters, current_user)
end
def filtered_schemes(schemes, search_term, filters)
FilterManager.filter_schemes(schemes, search_term, filters, current_user)
end
def bulk_upload
id = (logs_filters["bulk_upload_id"] || []).reject(&:blank?)[0]
@bulk_upload ||= current_user.bulk_uploads.find_by(id:)

33
app/views/organisations/schemes.html.erb

@ -11,21 +11,24 @@
) %>
<h2 class="govuk-visually-hidden">Supported housing schemes</h2>
<% end %>
<div class="app-filter-layout" data-controller="filter-layout">
<% if SchemePolicy.new(current_user, nil).create? %>
<%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %>
<% end %>
<%= govuk_details(
classes: "govuk-!-width-two-thirds",
summary_text: "What is a supported housing scheme?",
text: "A supported housing scheme (also known as a ‘supported housing service’) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people. A single scheme can contain multiple units, for example bedrooms in shared houses or a bungalow with 3 bedrooms.",
) %>
<%= render partial: "schemes/scheme_filters" %>
<div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by scheme name, code, postcode or location name", value: @searched) %>
<% if SchemePolicy.new(current_user, nil).create? %>
<%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %>
<% end %>
<%= govuk_details(
classes: "govuk-!-width-two-thirds",
summary_text: "What is a supported housing scheme?",
text: "A supported housing scheme (also known as a ‘supported housing service’) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people. A single scheme can contain multiple units, for example bedrooms in shared houses or a bungalow with 3 bedrooms.",
) %>
<%= render SearchComponent.new(current_user:, search_label: "Search by scheme name, code, postcode or location name", value: @searched) %>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
</div>
</div>

29
app/views/schemes/_scheme_filters.html.erb

@ -0,0 +1,29 @@
<div class="app-filter-layout__filter">
<div class="app-filter">
<div class="app-filter__header">
<h2 class="govuk-heading-m">Filters</h2>
</div>
<div class="app-filter__content">
<%= form_with url: schemes_path, html: { method: :get } do |f| %>
<div class="govuk-grid-row" style="white-space: nowrap">
<p class="govuk-grid-column-one-half">
<%= filters_applied_text(@filter_type) %>
</p>
<p class="govuk-!-text-align-right govuk-grid-column-one-half">
<%= reset_filters_link(@filter_type) %>
</p>
</div>
<%= render partial: "filters/checkbox_filter",
locals: {
f:,
options: scheme_status_filters,
label: "Status",
category: "status",
} %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>
</div>
</div>

14
app/views/schemes/index.html.erb

@ -8,11 +8,15 @@
<% if SchemePolicy.new(current_user, nil).create? %>
<%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %>
<% end %>
<div class="app-filter-layout" data-controller="filter-layout">
<%= render partial: "schemes/scheme_filters" %>
<div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by scheme name, code, postcode or location name", value: @searched) %>
<%= render SearchComponent.new(current_user:, search_label: "Search by scheme name, code, postcode or location name", value: @searched) %>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
</div>
</div>

4
spec/factories/scheme.rb

@ -25,5 +25,9 @@ FactoryBot.define do
trait :with_old_visible_id do
old_visible_id { rand(9_999_999) }
end
trait :incomplete do
confirmed { false }
support_type { nil }
end
end
end

33
spec/features/schemes_spec.rb

@ -63,6 +63,39 @@ RSpec.describe "Schemes scheme Features" do
end
end
end
context "when filtering schemes" do
context "when no filters are selected" do
it "displays the filters component with no clear button" do
expect(page).to have_content("No filters applied")
expect(page).not_to have_content("Clear")
end
end
context "when I have selected filters" do
before do
check("Active")
check("Incomplete")
click_button("Apply filters")
end
it "displays the filters component with a correct count and clear button" do
expect(page).to have_content("2 filters applied")
expect(page).to have_content("Clear")
end
context "when clearing the filters" do
before do
click_link("Clear")
end
it "clears the filters and displays the filter component as before" do
expect(page).to have_content("No filters applied")
expect(page).not_to have_content("Clear")
end
end
end
end
end
end
end

69
spec/models/scheme_spec.rb

@ -89,6 +89,75 @@ RSpec.describe Scheme, type: :model do
expect(described_class.search_by(location.name.downcase).first.locations.first.name).to eq(location.name)
end
end
context "when filtering by status" do
let!(:incomplete_scheme) { FactoryBot.create(:scheme, :incomplete) }
let(:active_scheme) { FactoryBot.create(:scheme) }
let(:deactivating_soon_scheme) { FactoryBot.create(:scheme) }
let(:deactivated_scheme) { FactoryBot.create(:scheme) }
let(:reactivating_soon_scheme) { FactoryBot.create(:scheme) }
before do
scheme.destroy!
scheme_1.destroy!
scheme_2.destroy!
Timecop.freeze(2022, 6, 7)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 8), scheme: deactivating_soon_scheme)
deactivating_soon_scheme.save!
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 6), scheme: deactivated_scheme)
deactivated_scheme.save!
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 7), reactivation_date: Time.zone.local(2022, 6, 8), scheme: reactivating_soon_scheme)
reactivating_soon_scheme.save!
FactoryBot.create(:location, scheme: active_scheme, confirmed: true)
end
after do
Timecop.unfreeze
end
context "when filtering by incomplete status" do
it "returns only incomplete schemes" do
expect(described_class.filter_by_status(%w[incomplete]).count).to eq(1)
expect(described_class.filter_by_status(%w[incomplete]).first).to eq(incomplete_scheme)
end
end
context "when filtering by active status" do
it "returns only active schemes" do
expect(described_class.filter_by_status(%w[active]).count).to eq(1)
expect(described_class.filter_by_status(%w[active]).first).to eq(active_scheme)
end
end
context "when filtering by deactivating_soon status" do
it "returns only deactivating_soon schemes" do
expect(described_class.filter_by_status(%w[deactivating_soon]).count).to eq(1)
expect(described_class.filter_by_status(%w[deactivating_soon]).first).to eq(deactivating_soon_scheme)
end
end
context "when filtering by deactivated status" do
it "returns only deactivated schemes" do
expect(described_class.filter_by_status(%w[deactivated]).count).to eq(1)
expect(described_class.filter_by_status(%w[deactivated]).first).to eq(deactivated_scheme)
end
end
context "when filtering by reactivating_soon status" do
it "returns only reactivating_soon schemes" do
expect(described_class.filter_by_status(%w[reactivating_soon]).count).to eq(1)
expect(described_class.filter_by_status(%w[reactivating_soon]).first).to eq(reactivating_soon_scheme)
end
end
context "when filtering by multiple statuses" do
it "returns relevant schemes" do
expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon]).count).to eq(2)
expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon])).to include(reactivating_soon_scheme)
expect(described_class.filter_by_status(%w[deactivating_soon reactivating_soon])).to include(deactivating_soon_scheme)
end
end
end
end
end

112
spec/requests/schemes_controller_spec.rb

@ -75,6 +75,65 @@ RSpec.describe SchemesController, type: :request do
end
end
end
context "when filtering" do
context "with status filter" do
let!(:incomplete_scheme) { create(:scheme, :incomplete, owning_organisation: user.organisation) }
let(:active_scheme) { create(:scheme, owning_organisation: user.organisation) }
let!(:deactivated_scheme) { create(:scheme, owning_organisation: user.organisation) }
before do
create(:location, scheme: active_scheme)
create(:scheme_deactivation_period, scheme: deactivated_scheme, deactivation_date: Time.zone.local(2022, 4, 1))
end
it "shows schemes for multiple selected statuses" do
get "/schemes?status[]=incomplete&status[]=active", headers:, params: {}
follow_redirect!
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered incomplete schemes" do
get "/schemes?status[]=incomplete", headers:, params: {}
follow_redirect!
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered active schemes" do
get "/schemes?status[]=active", headers:, params: {}
follow_redirect!
expect(page).to have_link(active_scheme.service_name)
expect(page).not_to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered deactivated schemes" do
get "/schemes?status[]=deactivated", headers:, params: {}
follow_redirect!
expect(page).to have_link(deactivated_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(incomplete_scheme.service_name)
end
it "does not reset the filters" do
get "/schemes?status[]=incomplete", headers:, params: {}
follow_redirect!
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
get "/schemes", headers:, params: {}
follow_redirect!
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
end
end
end
context "when signed in as a support user" do
@ -221,6 +280,59 @@ RSpec.describe SchemesController, type: :request do
expect(page).to have_title("Supported housing schemes (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
end
context "when filtering" do
context "with status filter" do
let!(:incomplete_scheme) { create(:scheme, :incomplete) }
let(:active_scheme) { create(:scheme) }
let!(:deactivated_scheme) { create(:scheme) }
before do
create(:location, scheme: active_scheme)
create(:scheme_deactivation_period, scheme: deactivated_scheme, deactivation_date: Time.zone.local(2022, 4, 1))
end
it "shows schemes for multiple selected statuses" do
get "/schemes?status[]=incomplete&status[]=active", headers:, params: {}
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered incomplete schemes" do
get "/schemes?status[]=incomplete", headers:, params: {}
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered active schemes" do
get "/schemes?status[]=active", headers:, params: {}
expect(page).to have_link(active_scheme.service_name)
expect(page).not_to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
it "shows filtered deactivated schemes" do
get "/schemes?status[]=deactivated", headers:, params: {}
expect(page).to have_link(deactivated_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(incomplete_scheme.service_name)
end
it "does not reset the filters" do
get "/schemes?status[]=incomplete", headers:, params: {}
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
get "/schemes", headers:, params: {}
expect(page).to have_link(incomplete_scheme.service_name)
expect(page).not_to have_link(active_scheme.service_name)
expect(page).not_to have_link(deactivated_scheme.service_name)
end
end
end
end
end

5
spec/views/organisations/schemes.html.erb_spec.rb

@ -4,10 +4,15 @@ RSpec.describe "organisations/schemes.html.erb" do
context "when data provider" do
let(:user) { build(:user) }
before do
session[:schemes_filters] = {}.to_json
end
it "does not render button to create schemes" do
assign(:organisation, user.organisation)
assign(:pagy, Pagy.new(count: 0, page: 1))
assign(:schemes, [])
assign(:filter_type, "schemes")
allow(view).to receive(:current_user).and_return(user)

5
spec/views/schemes/index.html.erb_spec.rb

@ -4,9 +4,14 @@ RSpec.describe "schemes/index.html.erb" do
context "when data provider" do
let(:user) { build(:user) }
before do
session[:schemes_filters] = {}.to_json
end
it "does not render button to create schemes" do
assign(:pagy, Pagy.new(count: 0, page: 1))
assign(:schemes, [])
assign(:filter_type, "schemes")
allow(view).to receive(:current_user).and_return(user)

Loading…
Cancel
Save