Browse Source

CLDC-3014 Add schemes and locations csv download functionality (#2083)

* feat: add schemes and locations download links and pages

* feat: update current path helper

* feat: update tests for different user visibility levels

* feat: update search caption tests

* refactor: lint tests

* refactor: lint tests

* git: revert unintentional inclusion

* feat: update tests

* refactor: lint

* feat: DRY up routing

* refactor: lint

* feat: add csv confirmation view

* feat: add scheme csv service

* feat: rename

* feat: update csv service

* feat: update csv service

* feat: update controller and rename view

* feat: update view

* refactor: lint

* feat: show correct headers in csv

* feat: add locations and combined csv behaviour

* feat: remove redundant user instance variable

* feat: add scheme csv service spec

* feat: add scheme email csv job tests

* feat: update filters in spec

* refactor: move scheme_email_csv_job_spec.rb

* feat: update spec

* refactor: remove blank line

* feat: add nowrap to all download links

* feat: update org schemes controller with org schemes (and rename for clarity)

* feat: update link indentation and spec

* feat: only include location LA name, and rename to location_local_authority

* feat: update seed locations with westminster local authorities to avoid similar confusion to some that arose in PO review

* feat: display multiple active periods on a single line

* feat: display multiple active periods on a single line

* feat: update line spacing in search captions

* feat: replace 2/3 with full column in download page

* feat: move scheme alphabeticising into manager

* feat: update tests now search/filterless copy has changed

* refactor: lint

* refactor: lint

* refactor: lint

* feat: add filter alphabeticising test

* feat: correct spacing
pull/2090/head
natdeanlewissoftwire 1 year ago committed by GitHub
parent
commit
9b19b1eedc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/components/search_result_caption_component.html.erb
  2. 22
      app/controllers/organisations_controller.rb
  3. 24
      app/controllers/schemes_controller.rb
  4. 4
      app/frontend/styles/_search.scss
  5. 2
      app/helpers/navigation_items_helper.rb
  6. 22
      app/helpers/schemes_helper.rb
  7. 29
      app/jobs/scheme_email_csv_job.rb
  8. 8
      app/models/user.rb
  9. 108
      app/services/csv/scheme_csv_service.rb
  10. 2
      app/services/filter_manager.rb
  11. 6
      app/views/logs/_log_list.html.erb
  12. 2
      app/views/logs/download_csv.html.erb
  13. 6
      app/views/organisations/schemes.html.erb
  14. 9
      app/views/schemes/_scheme_list.html.erb
  15. 15
      app/views/schemes/csv_confirmation.html.erb
  16. 16
      app/views/schemes/download_csv.html.erb
  17. 2
      app/views/schemes/index.html.erb
  18. 2
      app/views/users/_user_list.html.erb
  19. 9
      config/routes.rb
  20. 9
      db/seeds.rb
  21. 12
      spec/components/search_result_caption_component_spec.rb
  22. 4
      spec/features/organisation_spec.rb
  23. 4
      spec/features/schemes_spec.rb
  24. 2
      spec/fixtures/files/locations_csv_export.csv
  25. 2
      spec/fixtures/files/schemes_and_locations_csv_export.csv
  26. 2
      spec/fixtures/files/schemes_csv_export.csv
  27. 94
      spec/jobs/scheme_email_csv_job_spec.rb
  28. 4
      spec/requests/lettings_logs_controller_spec.rb
  29. 12
      spec/requests/organisation_relationships_controller_spec.rb
  30. 126
      spec/requests/organisations_controller_spec.rb
  31. 4
      spec/requests/sales_logs_controller_spec.rb
  32. 67
      spec/requests/schemes_controller_spec.rb
  33. 2
      spec/requests/users_controller_spec.rb
  34. 147
      spec/services/csv/scheme_csv_service_spec.rb
  35. 17
      spec/services/filter_manager_spec.rb

6
app/components/search_result_caption_component.html.erb

@ -1,4 +1,4 @@
<span class="govuk-!-margin-right-4"> <span>
<% if searched.present? && filters_count&.positive? %> <% if searched.present? && filters_count&.positive? %>
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching search and filters<br> <strong><%= count %></strong> <%= item_label.pluralize(count) %> matching search and filters<br>
<% elsif searched.present? %> <% elsif searched.present? %>
@ -6,6 +6,8 @@
<% elsif filters_count&.positive? %> <% elsif filters_count&.positive? %>
<strong><%= count %></strong> <%= item_label.pluralize(count) %> matching filters<br> <strong><%= count %></strong> <%= item_label.pluralize(count) %> matching filters<br>
<% else %> <% else %>
<strong><%= count %></strong> matching <%= item %> <span class="govuk-!-margin-right-4">
<strong><%= count %></strong> total <%= item %>
</span>
<% end %> <% end %>
</span> </span>

22
app/controllers/organisations_controller.rb

@ -7,9 +7,9 @@ class OrganisationsController < ApplicationController
before_action :find_resource, except: %i[index new create] before_action :find_resource, except: %i[index new create]
before_action :authenticate_scope!, except: [:index] 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, 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 schemes] before_action :session_filters, only: %i[users schemes email_schemes_csv download_schemes_csv]
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 }, 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 schemes] before_action -> { filter_manager.serialize_filters_to_session }, only: %i[users schemes email_schemes_csv download_schemes_csv]
def index def index
redirect_to organisation_path(current_user.organisation) unless current_user.support? redirect_to organisation_path(current_user.organisation) unless current_user.support?
@ -21,14 +21,26 @@ class OrganisationsController < ApplicationController
end end
def schemes def schemes
all_schemes = Scheme.where(owning_organisation: [@organisation] + @organisation.parent_organisations) organisation_schemes = Scheme.where(owning_organisation: [@organisation] + @organisation.parent_organisations)
@pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters).order_by_service_name) @pagy, @schemes = pagy(filter_manager.filtered_schemes(organisation_schemes, search_term, session_filters))
@searched = search_term.presence @searched = search_term.presence
@total_count = all_schemes.size @total_count = organisation_schemes.size
@filter_type = "schemes" @filter_type = "schemes"
end end
def download_schemes_csv
organisation_schemes = Scheme.where(owning_organisation: [@organisation] + @organisation.parent_organisations)
unpaginated_filtered_schemes = filter_manager.filtered_schemes(organisation_schemes, search_term, session_filters)
render "schemes/download_csv", locals: { search_term:, post_path: email_csv_schemes_path, download_type: params[:download_type], schemes: unpaginated_filtered_schemes }
end
def email_schemes_csv
SchemeEmailCsvJob.perform_later(current_user, search_term, session_filters, false, @organisation, params[:download_type])
redirect_to schemes_csv_confirmation_organisation_path
end
def show def show
redirect_to details_organisation_path(@organisation) redirect_to details_organisation_path(@organisation)
end end

24
app/controllers/schemes_controller.rb

@ -3,11 +3,11 @@ class SchemesController < ApplicationController
include Modules::SearchFilter include Modules::SearchFilter
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_resource, except: %i[index create new changes] before_action :find_resource, except: %i[index create new changes email_csv download_csv csv_confirmation]
before_action :redirect_if_scheme_confirmed, only: %i[primary_client_group confirm_secondary_client_group secondary_client_group support details] 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 :authorize_user, except: %i[email_csv download_csv csv_confirmation]
before_action :session_filters, if: :current_user, only: %i[index] before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index] before_action -> { filter_manager.serialize_filters_to_session }, if: :current_user, only: %i[index email_csv download_csv]
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
@ -15,7 +15,7 @@ class SchemesController < ApplicationController
redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support? redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support?
all_schemes = Scheme.all all_schemes = Scheme.all
@pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters).order_by_service_name) @pagy, @schemes = pagy(filter_manager.filtered_schemes(all_schemes, search_term, session_filters))
@searched = search_term.presence @searched = search_term.presence
@total_count = all_schemes.size @total_count = all_schemes.size
@filter_type = "schemes" @filter_type = "schemes"
@ -205,6 +205,20 @@ class SchemesController < ApplicationController
render "schemes/changes" render "schemes/changes"
end end
def download_csv
unpaginated_filtered_schemes = filter_manager.filtered_schemes(current_user.schemes, search_term, session_filters)
render "download_csv", locals: { search_term:, post_path: email_csv_schemes_path, download_type: params[:download_type], schemes: unpaginated_filtered_schemes }
end
def email_csv
all_orgs = params["organisation_select"] == "all"
SchemeEmailCsvJob.perform_later(current_user, search_term, session_filters, all_orgs, nil, params[:download_type])
redirect_to csv_confirmation_schemes_path
end
def csv_confirmation; end
private private
def authorize_user def authorize_user

4
app/frontend/styles/_search.scss

@ -22,3 +22,7 @@
margin-bottom: 2px; margin-bottom: 2px;
width: auto; width: auto;
} }
.app-search__caption {
line-height: govuk-spacing(7);
}

2
app/helpers/navigation_items_helper.rb

@ -57,7 +57,7 @@ private
end end
def supported_housing_schemes_current?(path) def supported_housing_schemes_current?(path)
path == schemes_path || path.include?("/schemes/") path.starts_with?(schemes_path)
end end
def non_support_supported_housing_schemes_current?(path) def non_support_supported_housing_schemes_current?(path)

22
app/helpers/schemes_helper.rb

@ -54,6 +54,28 @@ module SchemesHelper
end end
end end
def selected_schemes_and_locations_text(download_type, schemes)
scheme_count = schemes.count
case download_type
when "schemes"
"You've selected #{pluralize(scheme_count, 'scheme')}."
when "locations"
location_count = schemes.map(&:locations).flatten.count
"You've selected #{pluralize(location_count, 'location')} from #{pluralize(scheme_count, 'scheme')}."
when "combined"
location_count = schemes.map(&:locations).flatten.count
"You've selected #{pluralize(scheme_count, 'scheme')} with #{pluralize(location_count, 'location')}. The CSV will have one location per row with scheme details listed for each location."
end
end
def primary_schemes_csv_download_url(search, download_type)
csv_download_schemes_path(search:, download_type:)
end
def secondary_schemes_csv_download_url(organisation, search, download_type)
schemes_csv_download_organisation_path(organisation, search:, download_type:)
end
private private
ActivePeriod = Struct.new(:from, :to) ActivePeriod = Struct.new(:from, :to)

29
app/jobs/scheme_email_csv_job.rb

@ -0,0 +1,29 @@
class SchemeEmailCsvJob < ApplicationJob
queue_as :default
BYTE_ORDER_MARK = "\uFEFF".freeze # Required to ensure Excel always reads CSV as UTF-8
EXPIRATION_TIME = 24.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, download_type = "combined") # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_schemes = organisation.present? && user.support? ? Scheme.where(owning_organisation_id: organisation.id) : user.schemes
filtered_schemes = FilterManager.filter_schemes(unfiltered_schemes, search_term, filters, all_orgs, user)
csv_string = Csv::SchemeCsvService.new(download_type:).prepare_csv(filtered_schemes)
case download_type
when "schemes"
filename = "#{['schemes', organisation&.name, Time.zone.now].compact.join('-')}.csv"
when "locations"
filename = "#{['locations', organisation&.name, Time.zone.now].compact.join('-')}.csv"
when "combined"
filename = "#{['schemes-and-locations', organisation&.name, Time.zone.now].compact.join('-')}.csv"
end
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)
CsvDownloadMailer.new.send_csv_download_mail(user, url, EXPIRATION_TIME)
end
end

8
app/models/user.rb

@ -107,6 +107,14 @@ class User < ApplicationRecord
SalesLog.filter_by_managing_organisation(organisation.absorbed_organisations + [organisation]) SalesLog.filter_by_managing_organisation(organisation.absorbed_organisations + [organisation])
end end
def schemes
if support?
Scheme.all
else
Scheme.filter_by_owning_organisation(organisation.absorbed_organisations + [organisation])
end
end
def is_key_contact? def is_key_contact?
is_key_contact is_key_contact
end end

108
app/services/csv/scheme_csv_service.rb

@ -0,0 +1,108 @@
module Csv
class SchemeCsvService
include SchemesHelper
include LocationsHelper
def initialize(download_type:)
@download_type = download_type
end
def prepare_csv(schemes)
CSV.generate(headers: true) do |csv|
csv << attributes
schemes.find_each do |scheme|
if @download_type == "schemes"
csv << scheme_attributes.map { |attribute| scheme_value(attribute, scheme) }
else
scheme.locations.each do |location|
case @download_type
when "locations"
csv << [scheme.id_to_display] + location_attributes.map { |attribute| location_value(attribute, location) }
when "combined"
csv << scheme_attributes.map { |attribute| scheme_value(attribute, scheme) } + location_attributes.map { |attribute| location_value(attribute, location) }
end
end
end
end
end
end
private
SCHEME_FIELD_FROM_ATTRIBUTE = {
"scheme_code" => "id_to_display",
"scheme_service_name" => "service_name",
"scheme_status" => "status",
"scheme_sensitive" => "sensitive",
"scheme_registered_under_care_act" => "registered_under_care_act",
"scheme_support_services_provided_by" => "arrangement_type",
"scheme_primary_client_group" => "primary_client_group",
"scheme_has_other_client_group" => "has_other_client_group",
"scheme_secondary_client_group" => "secondary_client_group",
"scheme_support_type" => "support_type",
"scheme_intended_stay" => "intended_stay",
"scheme_created_at" => "created_at",
}.freeze
LOCATION_FIELD_FROM_ATTRIBUTE = {
"location_code" => "id",
"location_postcode" => "postcode",
"location_name" => "name",
"location_status" => "status",
"location_local_authority" => "location_admin_district",
"location_units" => "units",
"location_type_of_unit" => "type_of_unit",
"location_mobility_type" => "mobility_type",
}.freeze
CUSTOM_CALL_CHAINS = {
scheme_owning_organisation_name: %i[owning_organisation name],
}.freeze
SYSTEM_DATE_FIELDS = %w[
created_at
].freeze
def scheme_value(attribute, scheme)
attribute = SCHEME_FIELD_FROM_ATTRIBUTE.fetch(attribute, attribute)
if attribute == "scheme_active_dates"
scheme_availability(scheme).gsub("\n", ", ").to_s
elsif CUSTOM_CALL_CHAINS.key? attribute.to_sym
call_chain = CUSTOM_CALL_CHAINS[attribute.to_sym]
call_chain.reduce(scheme) { |object, next_call| object&.public_send(next_call) }
elsif SYSTEM_DATE_FIELDS.include? attribute
scheme.public_send(attribute)&.iso8601
else
scheme.public_send(attribute)
end
end
def location_value(attribute, location)
attribute = LOCATION_FIELD_FROM_ATTRIBUTE.fetch(attribute, attribute)
if attribute == "location_active_dates"
location_availability(location).gsub("\n", ", ").to_s
else
location.public_send(attribute)
end
end
def scheme_attributes
%w[scheme_code scheme_service_name scheme_status scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates]
end
def location_attributes
%w[location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates]
end
def attributes
case @download_type
when "schemes"
scheme_attributes
when "locations"
%w[scheme_code] + location_attributes
when "combined"
scheme_attributes + location_attributes
end
end
end
end

2
app/services/filter_manager.rb

@ -59,7 +59,7 @@ class FilterManager
schemes = schemes.public_send("filter_by_#{category}", values, user) schemes = schemes.public_send("filter_by_#{category}", values, user)
end end
schemes schemes.order_by_service_name
end end
def self.filter_locations(locations, search_term, filters, user) def self.filter_locations(locations, search_term, filters, user)

6
app/views/logs/_log_list.html.erb

@ -1,11 +1,11 @@
<h2 class="govuk-body"> <h2 class="govuk-body">
<div class="govuk-grid-row"> <div class="govuk-grid-row app-search__caption">
<div class="govuk-grid-column-three-quarters"> <div class="govuk-grid-column-three-quarters">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", filters_count: applied_filters_count(@filter_type))) %> <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", filters_count: applied_filters_count(@filter_type))) %>
<% if logs&.any? %> <% if logs&.any? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<% if @current_user.support? %> <% if @current_user.support? %>
<%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

2
app/views/logs/download_csv.html.erb

@ -5,7 +5,7 @@
<% end %> <% end %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-full">
<h1 class="govuk-heading-l">Download CSV</h1> <h1 class="govuk-heading-l">Download CSV</h1>
<p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p> <p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p>

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

@ -27,7 +27,11 @@
<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 } %> <% if current_user.support? %>
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, schemes_csv_download_url: secondary_schemes_csv_download_url(@organisation, @searched, "schemes"), locations_csv_download_url: secondary_schemes_csv_download_url(@organisation, @searched, "locations"), combined_csv_download_url: secondary_schemes_csv_download_url(@organisation, @searched, "combined") } %>
<% else %>
<%= render partial: "schemes/scheme_list", locals: { schemes: @schemes, title:, pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, schemes_csv_download_url: primary_schemes_csv_download_url(@searched, "schemes"), locations_csv_download_url: primary_schemes_csv_download_url(@searched, "locations"), combined_csv_download_url: primary_schemes_csv_download_url(@searched, "combined") } %>
<% end %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
</div> </div>

9
app/views/schemes/_scheme_list.html.erb

@ -1,8 +1,15 @@
<section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>"> <section class="app-table-group" tabindex="0" aria-labelledby="<%= title.dasherize %>">
<%= govuk_table do |table| %> <%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %> <span class="app-search__caption">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %>
<% if @schemes&.any? %>
<%= govuk_link_to "Download schemes (CSV)", schemes_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<%= govuk_link_to "Download locations (CSV)", locations_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<%= govuk_link_to "Download schemes and locations (CSV)", combined_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %>
<% end %> <% end %>
</span>
<% end %>
<%= table.head do |head| %> <%= table.head do |head| %>
<%= head.row do |row| %> <%= head.row do |row| %>
<% row.cell(header: true, text: "Scheme", html_attributes: { scope: "col", class: "govuk-!-width-one-quarter" }) %> <% row.cell(header: true, text: "Scheme", html_attributes: { scope: "col", class: "govuk-!-width-one-quarter" }) %>

15
app/views/schemes/csv_confirmation.html.erb

@ -0,0 +1,15 @@
<% content_for :title, "We’re sending you an email" %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= govuk_panel(title_text: "We’re sending you an email") %>
<p class="govuk-body">It should arrive in a few minutes, but it could take longer.</p>
<h2 class="govuk-heading-m">What happens next</h2>
<p class="govuk-body">Open your email inbox and click the link to download your CSV file.</p>
<p class="govuk-body">
<%= govuk_link_to "Return to schemes", schemes_path %>
</p>
</div>
</div>

16
app/views/schemes/download_csv.html.erb

@ -0,0 +1,16 @@
<% content_for :title, "Download CSV" %>
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-full">
<h1 class="govuk-heading-l">Download CSV</h1>
<p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p>
<p class="govuk-body"><%= selected_schemes_and_locations_text(download_type, schemes) %></p>
<%= govuk_button_to "Send email", post_path, method: :post, params: { search: search_term, download_type: } %>
</div>
</div>

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

@ -15,7 +15,7 @@
<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, schemes_csv_download_url: primary_schemes_csv_download_url(@searched, "schemes"), locations_csv_download_url: primary_schemes_csv_download_url(@searched, "locations"), combined_csv_download_url: primary_schemes_csv_download_url(@searched, "combined") } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "schemes" } %>
</div> </div>

2
app/views/users/_user_list.html.erb

@ -4,7 +4,7 @@
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", filters_count: applied_filters_count(@filter_type))) %> <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", filters_count: applied_filters_count(@filter_type))) %>
<% if current_user.support? %> <% if current_user.support? %>
<% query = searched.present? ? "?search=#{searched}" : nil %> <% query = searched.present? ? "?search=#{searched}" : nil %>
<%= govuk_link_to "Download (CSV)", "#{request.path}.csv#{query}", type: "text/csv" %> <%= govuk_link_to "Download (CSV)", "#{request.path}.csv#{query}", type: "text/csv", style: "white-space: nowrap" %>
<% end %> <% end %>
<% end %> <% end %>
<%= table.head do |head| %> <%= table.head do |head| %>

9
config/routes.rb

@ -79,6 +79,12 @@ Rails.application.routes.draw do
patch "deactivate", to: "schemes#deactivate" patch "deactivate", to: "schemes#deactivate"
patch "reactivate", to: "schemes#reactivate" patch "reactivate", to: "schemes#reactivate"
collection do
get "csv-download", to: "schemes#download_csv"
post "email-csv", to: "schemes#email_csv"
get "csv-confirmation", to: "schemes#csv_confirmation"
end
resources :locations do resources :locations do
post "locations", to: "locations#create" post "locations", to: "locations#create"
get "new-deactivation", to: "locations#new_deactivation" get "new-deactivation", to: "locations#new_deactivation"
@ -148,6 +154,9 @@ Rails.application.routes.draw do
post "sales-logs/email-csv", to: "organisations#email_sales_csv" post "sales-logs/email-csv", to: "organisations#email_sales_csv"
get "sales-logs/csv-confirmation", to: "sales_logs#csv_confirmation" get "sales-logs/csv-confirmation", to: "sales_logs#csv_confirmation"
get "schemes", to: "organisations#schemes" get "schemes", to: "organisations#schemes"
get "schemes/csv-download", to: "organisations#download_schemes_csv"
post "schemes/email-csv", to: "organisations#email_schemes_csv"
get "schemes/csv-confirmation", to: "schemes#csv_confirmation"
get "stock-owners", to: "organisation_relationships#stock_owners" get "stock-owners", to: "organisation_relationships#stock_owners"
get "stock-owners/add", to: "organisation_relationships#add_stock_owner" get "stock-owners/add", to: "organisation_relationships#add_stock_owner"
get "stock-owners/remove", to: "organisation_relationships#remove_stock_owner" get "stock-owners/remove", to: "organisation_relationships#remove_stock_owner"

9
db/seeds.rb

@ -301,7 +301,8 @@ unless Rails.env.test?
Location.create!( Location.create!(
scheme: scheme1, scheme: scheme1,
location_code: "S254-CU193AA", location_code: "E09000033",
location_admin_district: "Westminster",
postcode: "CU193AA", postcode: "CU193AA",
name: "Rectory Road", name: "Rectory Road",
type_of_unit: 4, type_of_unit: 4,
@ -311,7 +312,8 @@ unless Rails.env.test?
Location.create!( Location.create!(
scheme: scheme1, scheme: scheme1,
location_code: "S254-DM250DC", location_code: "E09000033",
location_admin_district: "Westminster",
postcode: "DM250DC", postcode: "DM250DC",
name: "Smithy Lane", name: "Smithy Lane",
type_of_unit: 1, type_of_unit: 1,
@ -321,7 +323,8 @@ unless Rails.env.test?
Location.create!( Location.create!(
scheme: scheme2, scheme: scheme2,
location_code: "S254-YX130WP", location_code: "E09000033",
location_admin_district: "Westminster",
postcode: "YX130WP", postcode: "YX130WP",
name: "Smithy Lane", name: "Smithy Lane",
type_of_unit: 2, type_of_unit: 2,

12
spec/components/search_result_caption_component_spec.rb

@ -11,7 +11,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
context "when search and filter results are found" do context "when search and filter results are found" do
it "renders table caption including the search results and total" do it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching search and filters<br>\n</span>\n") expect(result.to_html).to eq("<span>\n <strong>2</strong> users matching search and filters<br>\n</span>\n")
end end
end end
@ -19,7 +19,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:filters_count) { nil } let(:filters_count) { nil }
it "renders table caption including the search results and total" do it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching search<br>\n</span>\n") expect(result.to_html).to eq("<span>\n <strong>2</strong> users matching search<br>\n</span>\n")
end end
end end
@ -27,7 +27,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:searched) { nil } let(:searched) { nil }
it "renders table caption including the search results and total" do it "renders table caption including the search results and total" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> users matching filters<br>\n</span>\n") expect(result.to_html).to eq("<span>\n <strong>2</strong> users matching filters<br>\n</span>\n")
end end
end end
@ -36,7 +36,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:filters_count) { nil } let(:filters_count) { nil }
it "renders table caption with total count only" do it "renders table caption with total count only" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>#{count}</strong> matching #{item}\n</span>\n") expect(result.to_html).to eq("<span>\n <span class=\"govuk-!-margin-right-4\">\n <strong>2</strong> total schemes\n </span>\n</span>\n")
end end
end end
@ -44,7 +44,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:count) { 0 } let(:count) { 0 }
it "renders table caption with total count only" do it "renders table caption with total count only" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>0</strong> users matching search and filters<br>\n</span>\n") expect(result.to_html).to eq("<span>\n <strong>0</strong> users matching search and filters<br>\n</span>\n")
end end
end end
@ -52,7 +52,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do
let(:count) { 1 } let(:count) { 1 }
it "renders table caption with total count only" do it "renders table caption with total count only" do
expect(result.to_html).to eq("<span class=\"govuk-!-margin-right-4\">\n <strong>1</strong> user matching search and filters<br>\n</span>\n") expect(result.to_html).to eq("<span>\n <strong>1</strong> user matching search and filters<br>\n</span>\n")
end end
end end
end end

4
spec/features/organisation_spec.rb

@ -189,7 +189,7 @@ RSpec.describe "User Features" do
end end
it "has correct page details" do it "has correct page details" do
expect(page).to have_content("#{number_of_lettings_logs} matching logs") expect(page).to have_content("#{number_of_lettings_logs} total logs")
organisation.lettings_logs.map(&:id).each do |lettings_log_id| organisation.lettings_logs.map(&:id).each do |lettings_log_id|
expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}" expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}"
end end
@ -237,7 +237,7 @@ RSpec.describe "User Features" do
end end
it "can filter sales logs" do it "can filter sales logs" do
expect(page).to have_content("#{number_of_sales_logs} matching logs") expect(page).to have_content("#{number_of_sales_logs} total logs")
organisation.sales_logs.map(&:id).each do |sales_log_id| organisation.sales_logs.map(&:id).each do |sales_log_id|
expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}" expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}"
end end

4
spec/features/schemes_spec.rb

@ -573,7 +573,7 @@ RSpec.describe "Schemes scheme Features" do
it "displays information about a single location" do it "displays information about a single location" do
expect(page).to have_content "Locations" expect(page).to have_content "Locations"
expect(page).to have_content "#{scheme.locations.count} matching location" expect(page).to have_content "#{scheme.locations.count} total location"
end end
it "displays information about the first created location" do it "displays information about the first created location" do
@ -586,7 +586,7 @@ RSpec.describe "Schemes scheme Features" do
fill_in_and_save_second_location fill_in_and_save_second_location
click_button "Save and return to locations" click_button "Save and return to locations"
expect(page).to have_content "Locations" expect(page).to have_content "Locations"
expect(page).to have_content "#{scheme.locations.count} matching location" expect(page).to have_content "#{scheme.locations.count} total location"
end end
it "displays information about newly created location" do it "displays information about newly created location" do

2
spec/fixtures/files/locations_csv_export.csv vendored

@ -0,0 +1,2 @@
scheme_code,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates
,,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023"
1 scheme_code location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates
2 SW1A 2AA Downing Street deactivating_soon Westminster 20 Self-contained house Fitted with equipment and adaptations Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023

2
spec/fixtures/files/schemes_and_locations_csv_export.csv vendored

@ -0,0 +1,2 @@
scheme_code,scheme_service_name,scheme_status,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_support_services_provided_by,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,scheme_active_dates,location_code,location_postcode,location_name,location_status,location_local_authority,location_units,location_type_of_unit,location_mobility_type,location_active_dates
,Test name,active,Yes,Housing for older people,No,DLUHC,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023",,SW1A 2AA,Downing Street,deactivating_soon,Westminster,20,Self-contained house,Fitted with equipment and adaptations,"Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023"
1 scheme_code scheme_service_name scheme_status scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates
2 Test name active Yes Housing for older people No DLUHC The same organisation that owns the housing stock People with alcohol problems Yes Older people with support needs High level Medium stay 2021-04-01T00:00:00+01:00 Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023 SW1A 2AA Downing Street deactivating_soon Westminster 20 Self-contained house Fitted with equipment and adaptations Active from 1 April 2022 to 25 December 2023, Deactivated on 26 December 2023

2
spec/fixtures/files/schemes_csv_export.csv vendored

@ -0,0 +1,2 @@
scheme_code,scheme_service_name,scheme_status,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_support_services_provided_by,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,scheme_active_dates
,Test name,active,Yes,Housing for older people,No,DLUHC,The same organisation that owns the housing stock,People with alcohol problems,Yes,Older people with support needs,High level,Medium stay,2021-04-01T00:00:00+01:00,"Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023"
1 scheme_code scheme_service_name scheme_status scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates
2 Test name active Yes Housing for older people No DLUHC The same organisation that owns the housing stock People with alcohol problems Yes Older people with support needs High level Medium stay 2021-04-01T00:00:00+01:00 Active from 1 April 2020 to 31 March 2022, Deactivated on 1 April 2022, Active from 1 April 2023

94
spec/jobs/scheme_email_csv_job_spec.rb

@ -0,0 +1,94 @@
require "rails_helper"
describe SchemeEmailCsvJob do
include Helpers
test_url = :test_url
let(:job) { described_class.new }
let(:user) { FactoryBot.create(:user) }
let(:storage_service) { instance_double(Storage::S3Service) }
let(:mailer) { instance_double(CsvDownloadMailer) }
let(:scheme_csv_service) { instance_double(Csv::SchemeCsvService) }
let(:search_term) { "meaning" }
let(:filters) { { "owning_organisation" => organisation.id, "status" => %w[active] } }
let(:all_orgs) { false }
let(:organisation) { build(:organisation) }
let(:download_type) { "combined" }
let(:schemes) { build_list(:scheme, 5, owning_organisation: organisation) }
let(:locations) { build_list(:location, 5, scheme: schemes.first) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:write_file)
allow(storage_service).to receive(:get_presigned_url).and_return(test_url)
allow(Csv::SchemeCsvService).to receive(:new).and_return(scheme_csv_service)
allow(scheme_csv_service).to receive(:prepare_csv).and_return("")
allow(CsvDownloadMailer).to receive(:new).and_return(mailer)
allow(mailer).to receive(:send_csv_download_mail)
end
context "when exporting" do
before do
allow(FilterManager).to receive(:filter_schemes).and_return(schemes)
end
context "when download type schemes" do
let(:download_type) { "schemes" }
it "uses an appropriate filename in S3" do
expect(storage_service).to receive(:write_file).with(/schemes-.*\.csv/, anything)
job.perform(user)
end
end
context "when download type locations" do
let(:download_type) { "locations" }
it "uses an appropriate filename in S3" do
expect(storage_service).to receive(:write_file).with(/locations-.*\.csv/, anything)
job.perform(user)
end
end
context "when download type combined" do
let(:download_type) { "combined" }
it "uses an appropriate filename in S3" do
expect(storage_service).to receive(:write_file).with(/schemes-and-locations.*\.csv/, anything)
job.perform(user)
end
end
it "includes the organisation name in the filename when one is provided" do
expect(storage_service).to receive(:write_file).with(/schemes-and-locations-#{organisation.name}-.*\.csv/, anything)
job.perform(user, nil, {}, nil, organisation, "combined")
end
it "calls the filter manager with the arguments provided" do
expect(FilterManager).to receive(:filter_schemes).with(a_kind_of(ActiveRecord::Relation), search_term, filters, all_orgs, user)
job.perform(user, search_term, filters, all_orgs, organisation, "combined")
end
it "creates a SchemeCsvService with the correct download type" do
expect(Csv::SchemeCsvService).to receive(:new).with(download_type: "schemes")
job.perform(user, nil, {}, nil, nil, "schemes")
expect(Csv::SchemeCsvService).to receive(:new).with(download_type: "locations")
job.perform(user, nil, {}, nil, nil, "locations")
expect(Csv::SchemeCsvService).to receive(:new).with(download_type: "combined")
job.perform(user, nil, {}, nil, nil, "combined")
end
it "passes the schemes returned by the filter manager to the csv service" do
expect(scheme_csv_service).to receive(:prepare_csv).with(schemes)
job.perform(user, nil, {}, nil, nil, "combined")
end
end
it "sends an E-mail with the presigned URL and duration" do
expect(mailer).to receive(:send_csv_download_mail).with(user, test_url, instance_of(Integer))
job.perform(user)
end
end

4
spec/requests/lettings_logs_controller_spec.rb

@ -788,7 +788,7 @@ RSpec.describe LettingsLogsController, type: :request do
end end
it "shows the total log count" do it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> matching logs") expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total logs")
end end
it "does not show the pagination links" do it "does not show the pagination links" do
@ -882,7 +882,7 @@ RSpec.describe LettingsLogsController, type: :request do
end end
it "shows the total log count" do it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>26</strong> matching logs") expect(CGI.unescape_html(response.body)).to match("<strong>26</strong> total logs")
end end
it "has pagination links" do it "has pagination links" do

12
spec/requests/organisation_relationships_controller_spec.rb

@ -47,7 +47,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching stock owners") expect(page).to have_content("1 total stock owners")
end end
context "when adding a stock owner" do context "when adding a stock owner" do
@ -113,7 +113,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching agents") expect(page).to have_content("1 total agents")
end end
end end
@ -285,7 +285,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching stock owners") expect(page).to have_content("1 total stock owners")
end end
end end
@ -421,7 +421,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching agents") expect(page).to have_content("1 total agents")
end end
end end
@ -587,7 +587,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching stock owners") expect(page).to have_content("1 total stock owners")
end end
context "when adding a stock owner" do context "when adding a stock owner" do
@ -637,7 +637,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("1 matching agents") expect(page).to have_content("1 total agents")
end end
it "shows remove link(s)" do it "shows remove link(s)" do

126
spec/requests/organisations_controller_spec.rb

@ -59,6 +59,66 @@ RSpec.describe OrganisationsController, type: :request do
expect(page).to have_field("search", type: "search") expect(page).to have_field("search", type: "search")
end end
describe "scheme and location csv downloads" do
let!(:specific_organisation) { create(:organisation) }
let!(:specific_org_scheme) { create(:scheme, owning_organisation: specific_organisation) }
before do
create_list(:scheme, 5, owning_organisation: specific_organisation)
create_list(:location, 3, scheme: specific_org_scheme)
get "/organisations/#{specific_organisation.id}/schemes", headers:, params: {}
end
it "shows scheme and location download links" do
expect(page).to have_link("Download schemes (CSV)", href: schemes_csv_download_organisation_path(specific_organisation, download_type: "schemes"))
expect(page).to have_link("Download locations (CSV)", href: schemes_csv_download_organisation_path(specific_organisation, download_type: "locations"))
expect(page).to have_link("Download schemes and locations (CSV)", href: schemes_csv_download_organisation_path(specific_organisation, download_type: "combined"))
end
context "when there are no schemes for this organisation" do
before do
specific_organisation.owned_schemes.destroy_all
get "/organisations/#{specific_organisation.id}/schemes", headers:, params: {}
end
it "does not display CSV download links" do
expect(page).not_to have_link("Download schemes (CSV)")
expect(page).not_to have_link("Download locations (CSV)")
expect(page).not_to have_link("Download schemes and locations (CSV)")
end
end
context "when downloading scheme data" do
before do
get schemes_csv_download_organisation_path(specific_organisation, download_type: "schemes")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 6 schemes.")
end
end
context "when downloading location data" do
before do
get schemes_csv_download_organisation_path(specific_organisation, download_type: "locations")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 3 locations from 6 schemes.")
end
end
context "when downloading scheme and location data" do
before do
get schemes_csv_download_organisation_path(specific_organisation, download_type: "combined")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 6 schemes with 3 locations.")
end
end
end
it "has hidden accessibility field with description" do it "has hidden accessibility field with description" do
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing schemes</h2>" expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing schemes</h2>"
expect(CGI.unescape_html(response.body)).to include(expected_field) expect(CGI.unescape_html(response.body)).to include(expected_field)
@ -116,6 +176,62 @@ RSpec.describe OrganisationsController, type: :request do
expect(page).to have_field("search", type: "search") expect(page).to have_field("search", type: "search")
end end
describe "scheme and location csv downloads" do
before do
create_list(:scheme, 5, owning_organisation: user.organisation)
create_list(:location, 3, scheme: same_org_scheme)
end
it "shows scheme and location download links" do
expect(page).to have_link("Download schemes (CSV)", href: csv_download_schemes_path(download_type: "schemes"))
expect(page).to have_link("Download locations (CSV)", href: csv_download_schemes_path(download_type: "locations"))
expect(page).to have_link("Download schemes and locations (CSV)", href: csv_download_schemes_path(download_type: "combined"))
end
context "when there are no schemes for this organisation" do
before do
user.organisation.owned_schemes.destroy_all
get "/organisations/#{organisation.id}/schemes", headers:, params: {}
end
it "does not display CSV download links" do
expect(page).not_to have_link("Download schemes (CSV)")
expect(page).not_to have_link("Download locations (CSV)")
expect(page).not_to have_link("Download schemes and locations (CSV)")
end
end
context "when downloading scheme data" do
before do
get csv_download_schemes_path(download_type: "schemes")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 6 schemes.")
end
end
context "when downloading location data" do
before do
get csv_download_schemes_path(download_type: "locations")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 3 locations from 6 schemes.")
end
end
context "when downloading scheme and location data" do
before do
get csv_download_schemes_path(download_type: "combined")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 6 schemes with 3 locations.")
end
end
end
it "shows only schemes belonging to the same organisation" do it "shows only schemes belonging to the same organisation" do
expect(page).to have_content(same_org_scheme.id_to_display) expect(page).to have_content(same_org_scheme.id_to_display)
schemes.each do |scheme| schemes.each do |scheme|
@ -415,7 +531,7 @@ RSpec.describe OrganisationsController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("#{user.organisation.users.count} matching users") expect(page).to have_content("#{user.organisation.users.count} total users")
end end
end end
@ -799,7 +915,7 @@ RSpec.describe OrganisationsController, type: :request do
total_number_of_orgs = Organisation.all.count total_number_of_orgs = Organisation.all.count
expect(page).to have_link organisation.name, href: "organisations/#{organisation.id}/lettings-logs" expect(page).to have_link organisation.name, href: "organisations/#{organisation.id}/lettings-logs"
expect(page).to have_link unauthorised_organisation.name, href: "organisations/#{unauthorised_organisation.id}/lettings-logs" expect(page).to have_link unauthorised_organisation.name, href: "organisations/#{unauthorised_organisation.id}/lettings-logs"
expect(page).to have_content("#{total_number_of_orgs} matching organisations") expect(page).to have_content("#{total_number_of_orgs} total organisations")
end end
it "shows a search bar" do it "shows a search bar" do
@ -828,7 +944,7 @@ RSpec.describe OrganisationsController, type: :request do
end end
it "only shows logs for that organisation" do it "only shows logs for that organisation" do
expect(page).to have_content("#{total_number_of_org1_logs} matching logs") expect(page).to have_content("#{total_number_of_org1_logs} total logs")
organisation.lettings_logs.visible.map(&:id).each do |lettings_log_id| organisation.lettings_logs.visible.map(&:id).each do |lettings_log_id|
expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}" expect(page).to have_link lettings_log_id.to_s, href: "/lettings-logs/#{lettings_log_id}"
@ -982,7 +1098,7 @@ RSpec.describe OrganisationsController, type: :request do
end end
it "only shows logs for that organisation" do it "only shows logs for that organisation" do
expect(page).to have_content("#{number_of_org1_sales_logs} matching logs") expect(page).to have_content("#{number_of_org1_sales_logs} total logs")
organisation.sales_logs.map(&:id).each do |sales_log_id| organisation.sales_logs.map(&:id).each do |sales_log_id|
expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}" expect(page).to have_link sales_log_id.to_s, href: "/sales-logs/#{sales_log_id}"
end end
@ -1243,7 +1359,7 @@ RSpec.describe OrganisationsController, type: :request do
end end
it "shows the total organisations count" do it "shows the total organisations count" do
expect(CGI.unescape_html(response.body)).to match("<strong>#{total_organisations_count}</strong> matching organisations") expect(CGI.unescape_html(response.body)).to match("<strong>#{total_organisations_count}</strong> total organisations")
end end
it "has pagination links" do it "has pagination links" do

4
spec/requests/sales_logs_controller_spec.rb

@ -703,7 +703,7 @@ RSpec.describe SalesLogsController, type: :request do
end end
it "shows the total log count" do it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> matching logs") expect(CGI.unescape_html(response.body)).to match("<strong>1</strong> total logs")
end end
it "does not show the pagination links" do it "does not show the pagination links" do
@ -756,7 +756,7 @@ RSpec.describe SalesLogsController, type: :request do
end end
it "shows the total log count" do it "shows the total log count" do
expect(CGI.unescape_html(response.body)).to match("<strong>26</strong> matching logs") expect(CGI.unescape_html(response.body)).to match("<strong>26</strong> total logs")
end end
it "has pagination links" do it "has pagination links" do

67
spec/requests/schemes_controller_spec.rb

@ -191,6 +191,67 @@ RSpec.describe SchemesController, type: :request do
expect(page).to have_content("Schemes") expect(page).to have_content("Schemes")
end end
describe "scheme and location csv downloads" do
let!(:same_org_scheme) { create(:scheme, owning_organisation: user.organisation) }
let!(:specific_organisation) { create(:organisation) }
let!(:specific_org_scheme) { create(:scheme, owning_organisation: specific_organisation) }
before do
create(:location, scheme: same_org_scheme)
create_list(:scheme, 5, owning_organisation: specific_organisation)
create_list(:location, 3, scheme: specific_org_scheme)
end
it "shows scheme and location download links" do
expect(page).to have_link("Download schemes (CSV)", href: csv_download_schemes_path(download_type: "schemes"))
expect(page).to have_link("Download locations (CSV)", href: csv_download_schemes_path(download_type: "locations"))
expect(page).to have_link("Download schemes and locations (CSV)", href: csv_download_schemes_path(download_type: "combined"))
end
context "when there are no schemes for any organisation" do
before do
Scheme.destroy_all
get "/schemes"
end
it "does not display CSV download links" do
expect(page).not_to have_link("Download schemes (CSV)")
expect(page).not_to have_link("Download locations (CSV)")
expect(page).not_to have_link("Download schemes and locations (CSV)")
end
end
context "when downloading scheme data" do
before do
get csv_download_schemes_path(download_type: "schemes")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 12 schemes.")
end
end
context "when downloading location data" do
before do
get csv_download_schemes_path(download_type: "locations")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 9 locations from 12 schemes.")
end
end
context "when downloading scheme and location data" do
before do
get csv_download_schemes_path(download_type: "combined")
end
it "redirects to the correct download page" do
expect(page).to have_content("You've selected 12 schemes with 9 locations.")
end
end
end
it "shows all schemes" do it "shows all schemes" do
schemes.each do |scheme| schemes.each do |scheme|
expect(page).to have_content(scheme.id_to_display) expect(page).to have_content(scheme.id_to_display)
@ -236,7 +297,7 @@ RSpec.describe SchemesController, type: :request do
end end
it "shows the total organisations count" do it "shows the total organisations count" do
expect(CGI.unescape_html(response.body)).to match("<strong>#{schemes.count}</strong> matching schemes") expect(CGI.unescape_html(response.body)).to match("<strong>#{schemes.count}</strong> total schemes")
end end
context "when paginating over 20 results" do context "when paginating over 20 results" do
@ -252,7 +313,7 @@ RSpec.describe SchemesController, type: :request do
end end
it "shows the total schemes count" do it "shows the total schemes count" do
expect(CGI.unescape_html(response.body)).to match("<strong>#{total_schemes_count}</strong> matching schemes") expect(CGI.unescape_html(response.body)).to match("<strong>#{total_schemes_count}</strong> total schemes")
end end
it "shows which schemes are being shown on the current page" do it "shows which schemes are being shown on the current page" do
@ -277,7 +338,7 @@ RSpec.describe SchemesController, type: :request do
end end
it "shows the total schemes count" do it "shows the total schemes count" do
expect(CGI.unescape_html(response.body)).to match("<strong>#{total_schemes_count}</strong> matching schemes") expect(CGI.unescape_html(response.body)).to match("<strong>#{total_schemes_count}</strong> total schemes")
end end
it "has pagination links" do it "has pagination links" do

2
spec/requests/users_controller_spec.rb

@ -1194,7 +1194,7 @@ RSpec.describe UsersController, type: :request do
end end
it "shows the pagination count" do it "shows the pagination count" do
expect(page).to have_content("4 matching users") expect(page).to have_content("4 total users")
end end
it "shows the download csv link" do it "shows the download csv link" do

147
spec/services/csv/scheme_csv_service_spec.rb

@ -0,0 +1,147 @@
require "rails_helper"
RSpec.describe Csv::SchemeCsvService do
let(:organisation) { create(:organisation) }
let(:fixed_time) { Time.zone.local(2023, 6, 26) }
let(:scheme) { create(:scheme, :export, owning_organisation: organisation, service_name: "Test name") }
let(:location) { create(:location, :export, scheme:) }
let(:service) { described_class.new(download_type:) }
let(:download_type) { "combined" }
let(:csv) { CSV.parse(service.prepare_csv(Scheme.where(id: schemes.map(&:id)))) }
let(:schemes) { [scheme] }
let(:headers) { csv.first }
before do
Timecop.freeze(fixed_time)
create(:scheme_deactivation_period, scheme:, deactivation_date: scheme.created_at + 1.year, reactivation_date: scheme.created_at + 2.years)
create(:location_deactivation_period, location:, deactivation_date: location.created_at + 6.months)
end
after do
Timecop.return
end
it "returns a string" do
result = service.prepare_csv(Scheme.all)
expect(result).to be_a String
end
it "returns a csv with headers" do
expect(csv.first.first).to eq "scheme_code"
end
it "returns the correctly formatted scheme code" do
expect(csv.second.first.first).to eq "S"
end
context "when download type is schemes" do
let(:download_type) { "schemes" }
let(:scheme_attributes) { %w[scheme_code scheme_service_name scheme_status scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates] }
it "has the correct headers" do
expect(headers).to eq(scheme_attributes)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/schemes_csv_export.csv")
values_to_delete = %w[scheme_code]
values_to_delete.each do |attribute|
index = csv.first.index(attribute)
csv.second[index] = nil
end
expect(csv).to eq expected_content
end
context "when there are many schemes and locations" do
let(:schemes) { create_list(:scheme, scheme_count) }
let(:scheme_count) { 5 }
let(:locations_per_scheme) { 2 }
before do
schemes.each do |scheme|
create_list(:location, locations_per_scheme, scheme:)
end
end
it "creates a CSV with the correct number of schemes" do
expected_row_count_with_headers = scheme_count + 1
expect(csv.size).to be expected_row_count_with_headers
end
end
end
context "when download type is locations" do
let(:download_type) { "locations" }
let(:location_attributes) { %w[scheme_code location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates] }
it "has the correct headers" do
expect(headers).to eq(location_attributes)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/locations_csv_export.csv")
values_to_delete = %w[scheme_code location_code]
values_to_delete.each do |attribute|
index = csv.first.index(attribute)
csv.second[index] = nil
end
expect(csv).to eq expected_content
end
context "when there are many schemes and locations" do
let(:schemes) { create_list(:scheme, scheme_count) }
let(:scheme_count) { 5 }
let(:locations_per_scheme) { 2 }
before do
schemes.each do |scheme|
create_list(:location, locations_per_scheme, scheme:)
end
end
it "creates a CSV with the correct number of locations" do
expected_row_count_with_headers = locations_per_scheme * scheme_count + 1
expect(csv.size).to be expected_row_count_with_headers
end
end
end
context "when download type is combined" do
let(:combined_attributes) { %w[scheme_code scheme_service_name scheme_status scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_support_services_provided_by scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at scheme_active_dates location_code location_postcode location_name location_status location_local_authority location_units location_type_of_unit location_mobility_type location_active_dates] }
before do
scheme
end
it "has the correct headers" do
expect(headers).to eq(combined_attributes)
end
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/schemes_and_locations_csv_export.csv")
values_to_delete = %w[scheme_code location_code]
values_to_delete.each do |attribute|
index = csv.first.index(attribute)
csv.second[index] = nil
end
expect(csv).to eq expected_content
end
context "when there are many schemes and locations" do
let(:schemes) { create_list(:scheme, scheme_count) }
let(:scheme_count) { 5 }
let(:locations_per_scheme) { 2 }
before do
schemes.each do |scheme|
create_list(:location, locations_per_scheme, scheme:)
end
end
it "creates a CSV with the correct number of locations" do
expected_row_count_with_headers = locations_per_scheme * scheme_count + 1
expect(csv.size).to be expected_row_count_with_headers
end
end
end
end

17
spec/services/filter_manager_spec.rb

@ -77,4 +77,21 @@ describe FilterManager do
end end
end end
end end
describe "filter_schemes" do
let(:schemes) { create_list(:scheme, 5) }
let(:alphabetical_order_schemes) { [schemes[4], schemes[2], schemes[0], schemes[1], schemes[3]] }
before do
schemes[4].update!(service_name: "a")
schemes[2].update!(service_name: "bB")
schemes[0].update!(service_name: "C")
schemes[1].update!(service_name: "Dd")
schemes[3].update!(service_name: "e")
end
it "returns schemes in alphabetical order by service name" do
expect(described_class.filter_schemes(Scheme.all, nil, {}, nil, nil)).to eq(alphabetical_order_schemes)
end
end
end end

Loading…
Cancel
Save