Browse Source

Merge branch 'main' into SalesRetirementValidation

pull/2644/head
Rachael Booth 2 years ago committed by GitHub
parent
commit
90fb57a74e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Dockerfile
  2. 4
      Gemfile
  3. 7
      Gemfile.lock
  4. 90
      app/controllers/notifications_controller.rb
  5. 4
      app/helpers/application_helper.rb
  6. 2
      app/helpers/navigation_items_helper.rb
  7. 82
      app/helpers/notifications_helper.rb
  8. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_age_value_check.rb
  9. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_hhmemb_value_check.rb
  10. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_value_check.rb
  11. 4
      app/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check.rb
  12. 4
      app/models/form/lettings/pages/no_females_pregnant_household_person_value_check.rb
  13. 4
      app/models/form/lettings/pages/no_females_pregnant_household_value_check.rb
  14. 7
      app/models/form/sales/questions/person_known.rb
  15. 21
      app/models/notification.rb
  16. 12
      app/models/validations/soft_validations.rb
  17. 3
      app/presenters/homepage_presenter.rb
  18. 24
      app/services/postcode_service.rb
  19. 4
      app/views/home/index.html.erb
  20. 33
      app/views/notifications/_form.html.erb
  21. 8
      app/views/notifications/_notification_banner.html.erb
  22. 17
      app/views/notifications/_notification_home_section.html.erb
  23. 61
      app/views/notifications/check_answers.html.erb
  24. 27
      app/views/notifications/delete_confirmation.html.erb
  25. 3
      app/views/notifications/edit.html.erb
  26. 3
      app/views/notifications/new.html.erb
  27. 5
      app/views/notifications/show.html.erb
  28. 36
      config/forms/2021_2022.json
  29. 36
      config/forms/2022_2023.json
  30. 15
      config/locales/en.yml
  31. 5
      config/routes.rb
  32. 5
      db/migrate/20240904135306_add_show_additional_page_column_to_notifications.rb
  33. 15
      db/schema.rb
  34. 1
      spec/factories/notification.rb
  35. 22
      spec/features/notifications_spec.rb
  36. 7
      spec/features/start_page_spec.rb
  37. 2
      spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb
  38. 8
      spec/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check_spec.rb
  39. 8
      spec/models/form/lettings/pages/no_females_pregnant_household_person_value_check_spec.rb
  40. 28
      spec/models/form/sales/questions/person_known_spec.rb
  41. 4
      spec/models/lettings_log_derived_fields_spec.rb
  42. 11
      spec/models/lettings_log_spec.rb
  43. 37
      spec/models/notification_spec.rb
  44. 11
      spec/models/sales_log_spec.rb
  45. 12
      spec/models/validations/soft_validations_spec.rb
  46. 2
      spec/request_helper.rb
  47. 163
      spec/requests/notifications_controller_spec.rb
  48. 77
      spec/services/postcode_service_spec.rb
  49. 92
      yarn.lock

2
Dockerfile

@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle
# yarn: node package manager
# postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.15-r0 git=2.40.3-r0 bash=5.2.15-r5
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.16-r0 git=2.40.3-r0 bash=5.2.15-r5
# Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.3.14 --no-document

4
Gemfile

@ -23,6 +23,7 @@ gem "govuk-components", "~> 5.1"
gem "govuk_design_system_formbuilder", "~> 5.0"
# Convert Markdown into GOV.UK frontend-styled HTML
gem "govuk_markdown"
gem "redcarpet", "~> 3.6"
# GOV UK Notify
gem "notifications-ruby-client"
# A modest javascript framework for the html you already have
@ -38,7 +39,6 @@ gem "devise_two_factor_authentication"
# UK postcode parsing and validation
gem "uk_postcode"
# Get rich data from postcode lookups. Wraps postcodes.io
gem "postcodes_io"
# Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern
gem "view_component", "~> 3.9"
# Use the AWS S3 SDK as storage mechanism
@ -111,3 +111,5 @@ end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "cssbundling-rails"
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
gem "excon", "~> 0.111.0"

7
Gemfile.lock

@ -183,7 +183,7 @@ GEM
et-orbi (1.2.11)
tzinfo
event_stream_parser (1.0.0)
excon (0.109.0)
excon (0.111.0)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
@ -302,8 +302,6 @@ GEM
racc
pg (1.5.5)
possessive (1.0.1)
postcodes_io (0.4.0)
excon (~> 0.39)
propshaft (0.8.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
@ -533,6 +531,7 @@ DEPENDENCIES
devise_two_factor_authentication
dotenv-rails
erb_lint
excon (~> 0.111.0)
factory_bot_rails
faker
govuk-components (~> 5.1)
@ -549,7 +548,6 @@ DEPENDENCIES
parallel_tests
pg (~> 1.1)
possessive
postcodes_io
propshaft
pry-byebug
puma (~> 5.6)
@ -559,6 +557,7 @@ DEPENDENCIES
rack-mini-profiler (~> 2.0)
rails (~> 7.0.8.3)
rails_admin (~> 3.1)
redcarpet (~> 3.6)
redis (~> 4.8)
roo
rspec-rails

90
app/controllers/notifications_controller.rb

@ -1,19 +1,93 @@
class NotificationsController < ApplicationController
before_action :authenticate_user!, except: %i[show]
before_action :authenticate_scope!, except: %i[show dismiss]
before_action :find_notification, except: %i[new create]
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
def dismiss
if current_user.blank?
redirect_to root_path
else
current_user.newest_active_unread_notification.mark_as_read! for: current_user if current_user.newest_active_unread_notification.present?
redirect_back(fallback_location: root_path)
end
@notification.mark_as_read! for: current_user
redirect_back(fallback_location: root_path)
end
def show
@notification = current_user&.newest_active_unread_notification || Notification.newest_active_unauthenticated_notification
if @notification&.page_content
if !@notification.show_on_unauthenticated_pages && !current_user
render_not_found and return
end
if @notification.show_additional_page
render "show"
else
redirect_back(fallback_location: root_path)
end
end
def new
@notification = Notification.new
end
def create
@notification = Notification.new(notification_model_params)
if @notification.errors.empty? && @notification.save
redirect_to notification_check_answers_path(@notification)
else
render :new, status: :unprocessable_entity
end
end
def update
start_now = params[:notification][:start_now]
if @notification.errors.empty? && @notification.update(notification_model_params)
if start_now
flash[:notice] = "The notification has been created"
redirect_to root_path
else
redirect_to notification_check_answers_path(@notification)
end
elsif start_now
render :check_answers, status: :unprocessable_entity
else
render :edit, status: :unprocessable_entity
end
end
def delete
@notification.update!(end_date: Time.zone.now)
flash[:notice] = "The notification has been deleted"
redirect_to root_path
end
private
def notification_params
params.require(:notification).permit(:title, :show_on_unauthenticated_pages, :show_additional_page, :link_text, :page_content, :start_now)
end
def authenticate_scope!
render_not_found unless current_user.support?
end
def notification_model_params
model_params = notification_params.except(:start_now)
if notification_params[:show_additional_page] == "0"
model_params[:link_text] = nil
model_params[:page_content] = nil
end
model_params[:start_date] = Time.zone.now if notification_params[:start_now]
model_params
end
def find_notification
id = params[:id] || params[:notification_id]
@notification = current_user&.support? ? Notification.find_by(id:) : Notification.active.find_by(id:)
raise ActiveRecord::RecordNotFound unless @notification
@notification
end
end

4
app/helpers/application_helper.rb

@ -12,7 +12,7 @@ module ApplicationHelper
def govuk_header_classes(current_user)
if current_user&.support?
"app-header app-header--orange"
elsif ((current_user.blank? && Notification.active_unauthenticated_notifications.present?) || current_user&.active_unread_notifications.present?) && !current_page?(notifications_path)
elsif notifications_to_display?
"app-header app-header__no-border-bottom"
else
"app-header"
@ -28,7 +28,7 @@ module ApplicationHelper
end
def notifications_to_display?
!current_page?(notifications_path) && (authenticated_user_has_notifications? || unauthenticated_user_has_notifications?)
!request.path.match?(/\/notifications\/\d+$/) && (authenticated_user_has_notifications? || unauthenticated_user_has_notifications?)
end
private

2
app/helpers/navigation_items_helper.rb

@ -47,7 +47,7 @@ module NavigationItemsHelper
private
def home_current?(path)
path == root_path || path == notifications_path
path == root_path || path.match?(/\/notifications\/\d+$/)
end
def lettings_logs_current?(path)

82
app/helpers/notifications_helper.rb

@ -14,4 +14,86 @@ module NotificationsHelper
Notification.newest_active_unauthenticated_notification
end
end
def render_for_banner(title)
# rubocop:disable Rails/HelperInstanceVariable
@banner_renderer ||= NotificationRenderer.new({ invert_link_colour: true, bold_all_text: true })
@banner_markdown ||= Redcarpet::Markdown.new(@banner_renderer, no_intra_emphasis: true)
@banner_markdown.render(title)
# rubocop:enable Rails/HelperInstanceVariable
end
def render_for_summary(title)
render_normal_markdown(title)
end
def render_for_page(notification)
content_includes_own_title = /\A\s*#[^#]/.match?(notification.page_content)
return render_normal_markdown(notification.page_content) if content_includes_own_title
content = "# #{notification.title}\n#{notification.page_content}"
render_normal_markdown(content)
end
def render_for_home(notification)
return render_normal_markdown(notification.title) unless notification.show_additional_page
content = "#{notification.title} \n[#{notification.link_text}](#{notification_path(notification)})"
render_normal_markdown(content)
end
private
def render_normal_markdown(content)
# rubocop:disable Rails/HelperInstanceVariable
@on_page_renderer ||= NotificationRenderer.new({ invert_link_colour: false, bold_all_text: false })
@on_page_markdown ||= Redcarpet::Markdown.new(@on_page_renderer, no_intra_emphasis: true)
@on_page_markdown.render(content)
# rubocop:enable Rails/HelperInstanceVariable
end
end
class NotificationRenderer < Redcarpet::Render::HTML
def initialize(options = {})
link_class = "govuk-link"
link_class += " govuk-link--inverse" if options[:invert_link_colour]
@bold = options[:bold_all_text] # rubocop:disable Rails/HelperInstanceVariable
base_options = { escape_html: true, safe_links_only: true, link_attributes: { class: link_class } }
super base_options
end
def header(text, header_level)
header_size = case header_level
when 1
"xl"
when 2
"l"
when 3
"m"
else
"s"
end
%(<h#{header_level} class="govuk-heading-#{header_size}">#{text}</h#{header_level}>)
end
def paragraph(text)
return %(<p class="govuk-!-font-weight-bold">#{text}</p>) if @bold # rubocop:disable Rails/HelperInstanceVariable
%(<p class="govuk-body-m">#{text}</p>)
end
def list(contents, list_type)
return %(<ol class="govuk-list govuk-list--number">#{contents}</ol>) if list_type == :ordered
%(<ul class="govuk-list govuk-list--bullet">#{contents}</ul>)
end
def hrule
%(<hr class="govuk-section-break govuk-section-break--xl govuk-section-break--visible">)
end
def block_quote(quote)
%(<div class="govuk-inset-text">#{quote}</div>)
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_age_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadAgeValueCheck < ::For
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "no_females_pregnant_household_lead_age_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_hhmemb_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadHhmembValueCheck < ::
def initialize(id, hsh, subsection)
super
@id = "no_females_pregnant_household_lead_hhmemb_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadValueCheck < ::Form::
def initialize(id, hsh, subsection)
super
@id = "no_females_pregnant_household_lead_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
end

4
app/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck < ::F
def initialize(id, hsh, subsection, person_index:)
super(id, hsh, subsection)
@id = "no_females_pregnant_household_person_#{person_index}_age_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true, "age#{person_index}_known" => 0 }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true, "age#{person_index}_known" => 0 }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@person_index = person_index

4
app/models/form/lettings/pages/no_females_pregnant_household_person_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck < ::Form
def initialize(id, hsh, subsection, person_index:)
super(id, hsh, subsection)
@id = "no_females_pregnant_household_person_#{person_index}_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true, "details_known_#{person_index}" => 0 }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true, "details_known_#{person_index}" => 0 }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@person_index = person_index

4
app/models/form/lettings/pages/no_females_pregnant_household_value_check.rb

@ -2,13 +2,13 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdValueCheck < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "no_females_pregnant_household_value_check"
@depends_on = [{ "no_females_in_a_pregnant_household?" => true }]
@depends_on = [{ "all_male_tenants_in_a_pregnant_household?" => true }]
@title_text = {
"translation" => "soft_validations.pregnancy.title",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
@informative_text = {
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [{ "key" => "sex1", "label" => true, "i18n_template" => "sex1" }],
}
end

7
app/models/form/sales/questions/person_known.rb

@ -5,13 +5,6 @@ class Form::Sales::Questions::PersonKnown < ::Form::Question
@header = "Do you know the details for person #{person_index}?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hidden_in_check_answers = {
"depends_on" => [
{
"details_known_#{person_index}" => 1,
},
],
}
@check_answers_card_number = person_index
end

21
app/models/notification.rb

@ -1,7 +1,12 @@
class Notification < ApplicationRecord
acts_as_readable
scope :active, -> { where("start_date <= ? AND end_date >= ?", Time.zone.now, Time.zone.now) }
has_paper_trail
validates :title, presence: { message: I18n.t("activerecord.errors.models.notification.attributes.title.blank") }
validate :validate_additional_page_information
scope :active, -> { where("start_date <= ? AND (end_date >= ? OR end_date is null)", Time.zone.now, Time.zone.now) }
scope :unauthenticated, -> { where(show_on_unauthenticated_pages: true) }
def self.active_unauthenticated_notifications
@ -11,4 +16,18 @@ class Notification < ApplicationRecord
def self.newest_active_unauthenticated_notification
active_unauthenticated_notifications.last
end
private
def validate_additional_page_information
return unless show_additional_page
if link_text.blank?
errors.add :link_text, I18n.t("activerecord.errors.models.notification.attributes.link_text.blank_when_additional_page_set")
end
if page_content.blank?
errors.add :page_content, I18n.t("activerecord.errors.models.notification.attributes.page_content.blank_when_additional_page_set")
end
end
end

12
app/models/validations/soft_validations.rb

@ -67,8 +67,8 @@ module Validations::SoftValidations
end
end
def no_females_in_a_pregnant_household?
!females_in_the_household? && all_tenants_gender_information_completed? && preg_occ == 1
def all_male_tenants_in_a_pregnant_household?
all_male_tenants_in_the_household? && all_tenants_gender_information_completed? && preg_occ == 1
end
def female_in_pregnant_household_in_soft_validation_range?
@ -226,6 +226,14 @@ private
end
end
def all_male_tenants_in_the_household?
person_count = hhmemb || 8
(1..person_count).all? do |n|
public_send("sex#{n}") == "M"
end
end
def tenant_is_retired?(economic_status)
economic_status == 5
end

3
app/presenters/homepage_presenter.rb

@ -2,7 +2,7 @@ class HomepagePresenter
include Rails.application.routes.url_helpers
include CollectionTimeHelper
attr_reader :current_year_in_progress_lettings_data, :current_year_completed_lettings_data, :current_year_in_progress_sales_data, :current_year_completed_sales_data, :last_year_in_progress_lettings_data, :last_year_completed_lettings_data, :last_year_in_progress_sales_data, :last_year_completed_sales_data, :incomplete_schemes_data
attr_reader :current_year_in_progress_lettings_data, :current_year_completed_lettings_data, :current_year_in_progress_sales_data, :current_year_completed_sales_data, :last_year_in_progress_lettings_data, :last_year_completed_lettings_data, :last_year_in_progress_sales_data, :last_year_completed_sales_data, :incomplete_schemes_data, :active_notifications
def initialize(user)
@user = user
@ -25,6 +25,7 @@ class HomepagePresenter
path: schemes_path(status: [:incomplete], owning_organisation_select: "all"),
}
end
@active_notifications = Notification.active if @user.support?
end
def title_text_for_user

24
app/services/postcode_service.rb

@ -1,26 +1,24 @@
class PostcodeService
def initialize
@pio = Postcodes::IO.new
end
def lookup(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
result = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = self.class.clean(postcode)
Timeout.timeout(30) { postcode_lookup = @pio.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")
end
if postcode_lookup && postcode_lookup.info.present?
{
location_code: postcode_lookup.codes["admin_district"],
location_admin_district: postcode_lookup&.admin_district,
response = Excon.get("https://api.postcodes.io/postcodes/#{ascii_postcode}", idempotent: true, timeout: 30, expects: [200])
parsed_response = JSON.parse(response.body)
result = {
location_code: parsed_response["result"]["codes"]["admin_district"],
location_admin_district: parsed_response["result"]["admin_district"],
}
rescue Excon::Error => e
Rails.logger.warn("Postcode lookup request was not successful: #{e} #{e.response.body}")
rescue StandardError => e
Rails.logger.error("Unexpected error with postcode lookup request: #{e}")
end
result
end
def self.clean(postcode)

4
app/views/home/index.html.erb

@ -6,6 +6,10 @@
<h1 class="govuk-heading-l"><%= @homepage_presenter.title_text_for_user %></h1>
</div>
<% if @current_user.support? %>
<%= render partial: "notifications/notification_home_section", locals: { active_notifications: @homepage_presenter.active_notifications } %>
<% end %>
<div class="app-data-box-section govuk-grid-row">
<% if @homepage_presenter.in_crossover_period? %>

33
app/views/notifications/_form.html.erb

@ -0,0 +1,33 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<% content_for :page_title, "Create a new notification" %>
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<h1 class="govuk-heading-l">
<%= content_for(:page_title) %>
</h1>
<span class="govuk-caption-m govuk-!-margin-bottom-6">This notification will be visible to all users until you delete it</span>
<%= f.govuk_text_field :title,
label: { text: "Title", size: "m" },
hint: { text: "Use markdown for links to existing pages" } %>
<%= f.govuk_check_boxes_fieldset :display_options, multiple: false, legend: { text: "Display Options" } do %>
<%= f.govuk_check_box :show_on_unauthenticated_pages, 1, 0, multiple: false, label: { text: "Show this notification on unauthenticated pages, for example the start page" } %>
<%= f.govuk_check_box :show_additional_page, 1, 0, multiple: false, label: { text: "Include a link to a separate page with additional information" } do %>
<%= f.govuk_text_field :link_text, label: { text: "Link text" }, hint: { text: "Use descriptive language and relevant terms. The link text should make sense out of context." } %>
<%= f.govuk_text_area :page_content, label: { text: "Page content" }, hint: { text: "Use markdown to format the page content. The page title will be the notification title by default. Use a heading level one if you want to override it." } %>
<% end %>
<% end %>
<span class="govuk-caption-m govuk-!-margin-bottom-6"><%= govuk_link_to "Find out more about using Markdown at Markdown Guide", "https://www.markdownguide.org/basic-syntax/", new_tab: true %></span>
<%= f.govuk_submit "Continue" %>
</div>
</div>

8
app/views/notifications/_notification_banner.html.erb

@ -6,16 +6,16 @@
<% if notification_count > 1 && current_user.present? %>
<p>Notification 1 of <%= notification_count %></p>
<% end %>
<p class="govuk-!-font-weight-bold"><%= notification.title.html_safe %></p>
<% if notification.page_content.present? %>
<%== render_for_banner(notification.title) %>
<% if notification.show_additional_page %>
<div class="govuk-body">
<%= govuk_link_to notification.link_text, notifications_path, class: "govuk-link--inverse govuk-!-font-weight-bold" %>
<%= govuk_link_to notification.link_text, notification_path(notification), class: "govuk-link--inverse govuk-!-font-weight-bold" %>
</div>
<% end %>
</div>
<% if current_user.present? %>
<p class="govuk-grid-column-one-quarter govuk-!-text-align-right ">
<%= govuk_link_to "Dismiss", dismiss_notifications_path, class: "govuk-link--inverse" %>
<%= govuk_link_to "Dismiss", notification_dismiss_path(notification), class: "govuk-link--inverse" %>
</p>
<% end %>
</div>

17
app/views/notifications/_notification_home_section.html.erb

@ -0,0 +1,17 @@
<div class="govuk-grid-row">
<p class="govuk-!-font-weight-bold"><%== I18n.t("active_notifications", count: active_notifications.count) %></p>
<% active_notifications.each do |notification| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters">
<%== render_for_home(notification) %>
</div>
<div class="govuk-grid-column-one-quarter">
<%= govuk_link_to("Delete notification", notification_delete_confirmation_path(notification), class: "app-!-colour-red") %>
</div>
</div>
<% end %>
</div>
<div class="govuk-grid-row">
<%= govuk_button_link_to "Create a new notification", new_notification_path %>
</div>

61
app/views/notifications/check_answers.html.erb

@ -0,0 +1,61 @@
<% content_for :title, "Create a notification" %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters-from-desktop">
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l">Create a notification</span>
Check your answers
</h1>
<span class="govuk-caption-m govuk-!-margin-bottom-6">This notification will be visible to all users until you delete it</span>
<%= form_for(@notification, method: :patch) do |f| %>
<%= f.govuk_error_summary %>
<div class="govuk-summary-card">
<div class="govuk-summary-card__content">
<%= govuk_summary_list do |summary_list| %>
<% summary_list.with_row do |row| %>
<% row.with_key { "Title" } %>
<% row.with_value do %>
<%== render_for_summary(@notification.title) %>
<% end %>
<% row.with_action(text: "Change", href: edit_notification_path(@notification)) %>
<% end %>
<% summary_list.with_row do |row| %>
<% row.with_key { "Show on unauthenticated pages?" } %>
<% row.with_value { @notification.show_on_unauthenticated_pages ? "Yes" : "No" } %>
<% row.with_action(text: "Change", href: edit_notification_path(@notification)) %>
<% end %>
<% summary_list.with_row do |row| %>
<% row.with_key { "Link to additional information?" } %>
<% row.with_value { @notification.show_additional_page ? "Yes" : "No" } %>
<% row.with_action(text: "Change", href: edit_notification_path(@notification)) %>
<% end %>
<% if @notification.show_additional_page %>
<% summary_list.with_row do |row| %>
<% row.with_key { "Link text" } %>
<% row.with_value { @notification.link_text } %>
<% row.with_action(text: "Change", href: edit_notification_path(@notification)) %>
<% end %>
<% summary_list.with_row do |row| %>
<% row.with_key { "Page content" } %>
<% row.with_value do %>
<%= govuk_link_to "View page preview", notification_path(@notification), new_tab: true %>
<% end %>
<% row.with_action(text: "Change", href: edit_notification_path(@notification)) %>
<% end %>
<% end %>
<% end %>
</div>
</div>
<%= f.hidden_field :start_now, value: true %>
<%= f.govuk_submit "Create notification" %>
<% end %>
</div>
</div>

27
app/views/notifications/delete_confirmation.html.erb

@ -0,0 +1,27 @@
<% content_for :before_content do %>
<% content_for :title, "Are you sure you want to delete this notification?" %>
<%= govuk_back_link(href: :back) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-xl">
<%= content_for(:title) %>
</h1>
<p class="govuk-body"><%== render_for_summary("**Notification:** #{@notification.title}") %></p>
<p class="govuk-body">Users will no longer see this notification.</p>
<%= govuk_warning_text(text: "You will not be able to undo this action.") %>
<div class="govuk-button-group">
<%= govuk_button_to(
"Delete notification",
notification_delete_path(@notification),
method: :delete,
) %>
<%= govuk_button_link_to "Cancel", root_path, html: { method: :get }, secondary: true %>
</div>
</div>
</div>

3
app/views/notifications/edit.html.erb

@ -0,0 +1,3 @@
<%= form_for(@notification, as: :notification, method: :patch) do |f| %>
<% render partial: "form", locals: { notification: @notification, f: } %>
<% end %>

3
app/views/notifications/new.html.erb

@ -0,0 +1,3 @@
<%= form_for(@notification, as: :notification, method: :post) do |f| %>
<% render partial: "form", locals: { notification: @notification, f: } %>
<% end %>

5
app/views/notifications/show.html.erb

@ -5,10 +5,7 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl"><%= @notification.title %></h1>
<p>
<%= sanitize @notification.page_content %>
</p>
<%== render_for_page(@notification) %>
</div>
</div>
<br>

36
config/forms/2021_2022.json

@ -1170,7 +1170,7 @@
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -1312,7 +1312,7 @@
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -1433,7 +1433,7 @@
"no_females_pregnant_household_lead_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -2091,7 +2091,7 @@
"no_females_pregnant_household_person_2_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age2_known": 0
}
],
@ -2214,7 +2214,7 @@
"no_females_pregnant_household_person_2_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_2": 0
}
],
@ -2626,7 +2626,7 @@
"no_females_pregnant_household_person_3_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age3_known": 0
}
],
@ -2749,7 +2749,7 @@
"no_females_pregnant_household_person_3_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_3": 0
}
],
@ -3158,7 +3158,7 @@
"no_females_pregnant_household_person_4_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age4_known": 0
}
],
@ -3281,7 +3281,7 @@
"no_females_pregnant_household_person_4_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_4": 0
}
],
@ -3687,7 +3687,7 @@
"no_females_pregnant_household_person_5_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age5_known": 0
}
],
@ -3810,7 +3810,7 @@
"no_females_pregnant_household_person_5_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_5": 0
}
],
@ -4213,7 +4213,7 @@
"no_females_pregnant_household_person_6_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age6_known": 0
}
],
@ -4336,7 +4336,7 @@
"no_females_pregnant_household_person_6_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_6": 0
}
],
@ -4736,7 +4736,7 @@
"no_females_pregnant_household_person_7_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age7_known": 0
}
],
@ -4859,7 +4859,7 @@
"no_females_pregnant_household_person_7_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_7": 0
}
],
@ -5256,7 +5256,7 @@
"no_females_pregnant_household_person_8_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age8_known": 0
}
],
@ -5379,7 +5379,7 @@
"no_females_pregnant_household_person_8_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_8": 0
}
],
@ -5794,7 +5794,7 @@
"no_females_pregnant_household_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {

36
config/forms/2022_2023.json

@ -1200,7 +1200,7 @@
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -1372,7 +1372,7 @@
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -1523,7 +1523,7 @@
"no_females_pregnant_household_lead_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {
@ -2163,7 +2163,7 @@
"no_females_pregnant_household_person_2_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age2_known": 0
}
],
@ -2316,7 +2316,7 @@
"no_females_pregnant_household_person_2_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_2": 0
}
],
@ -2746,7 +2746,7 @@
"no_females_pregnant_household_person_3_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age3_known": 0
}
],
@ -2899,7 +2899,7 @@
"no_females_pregnant_household_person_3_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_3": 0
}
],
@ -3326,7 +3326,7 @@
"no_females_pregnant_household_person_4_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age4_known": 0
}
],
@ -3479,7 +3479,7 @@
"no_females_pregnant_household_person_4_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_4": 0
}
],
@ -3903,7 +3903,7 @@
"no_females_pregnant_household_person_5_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age5_known": 0
}
],
@ -4056,7 +4056,7 @@
"no_females_pregnant_household_person_5_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_5": 0
}
],
@ -4477,7 +4477,7 @@
"no_females_pregnant_household_person_6_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age6_known": 0
}
],
@ -4630,7 +4630,7 @@
"no_females_pregnant_household_person_6_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_6": 0
}
],
@ -5048,7 +5048,7 @@
"no_females_pregnant_household_person_7_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age7_known": 0
}
],
@ -5201,7 +5201,7 @@
"no_females_pregnant_household_person_7_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_7": 0
}
],
@ -5616,7 +5616,7 @@
"no_females_pregnant_household_person_8_age_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"age8_known": 0
}
],
@ -5769,7 +5769,7 @@
"no_females_pregnant_household_person_8_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true,
"all_male_tenants_in_a_pregnant_household?": true,
"details_known_8": 0
}
],
@ -6205,7 +6205,7 @@
"no_females_pregnant_household_value_check": {
"depends_on": [
{
"no_females_in_a_pregnant_household?": true
"all_male_tenants_in_a_pregnant_household?": true
}
],
"title_text": {

15
config/locales/en.yml

@ -41,6 +41,11 @@ en:
create_password: "Create a password to finish setting up your account"
reset_password: "Reset your password"
active_notifications:
zero: "There are no active notifications"
one: "There is one active notification:"
other: "There are %{count} active notifications:"
activemodel:
errors:
models:
@ -185,6 +190,14 @@ en:
invalid: "Enter a valid merge date"
existing_absorbing_organisation:
blank: "You must answer absorbing organisation already active?"
notification:
attributes:
title:
blank: "Enter a title"
link_text:
blank_when_additional_page_set: "Enter the link text"
page_content:
blank_when_additional_page_set: "Enter the page content"
notification:
logs_deleted:
@ -710,7 +723,7 @@ Make sure these answers are correct."
hint_text: "This is higher than the purchase price minus the discount."
pregnancy:
title: "You told us somebody in the household is pregnant"
no_females: "You also told us there are no female tenants living at the property."
all_male_tenants: "You also told us that all the tenants living at the property are male."
females_not_in_soft_age_range: "You also told us that any female tenants living at the property are in the following age ranges:<ul><li>under 16 years old</li><li>over 50 years old</li></ul>"
major_repairs_date:
title_text: "You told us the property has been vacant for 2 years."

5
config/routes.rb

@ -142,8 +142,11 @@ Rails.application.routes.draw do
end
end
resource :notifications do
resources :notifications do
get "dismiss", to: "notifications#dismiss"
get "check-answers", to: "notifications#check_answers"
get "delete-confirmation", to: "notifications#delete_confirmation"
delete "delete", to: "notifications#delete"
end
resources :organisations do

5
db/migrate/20240904135306_add_show_additional_page_column_to_notifications.rb

@ -0,0 +1,5 @@
class AddShowAdditionalPageColumnToNotifications < ActiveRecord::Migration[7.0]
def change
add_column :notifications, :show_additional_page, :boolean
end
end

15
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2024_09_11_152702) do
ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -55,7 +55,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_11_152702) do
t.datetime "last_accessed"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.check_constraint "log_type::text = ANY (ARRAY['lettings'::character varying::text, 'sales'::character varying::text])", name: "log_type_check"
t.check_constraint "log_type::text = ANY (ARRAY['lettings'::character varying, 'sales'::character varying]::text[])", name: "log_type_check"
t.check_constraint "year >= 2000 AND year <= 2099", name: "year_check"
end
@ -206,14 +206,14 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_11_152702) do
t.integer "hb"
t.integer "hbrentshortfall"
t.integer "property_relet"
t.datetime "mrcdate", precision: nil
t.datetime "mrcdate"
t.integer "incref"
t.datetime "startdate", precision: nil
t.datetime "startdate"
t.integer "armedforces"
t.integer "first_time_property_let_as_social_housing"
t.integer "unitletas"
t.integer "builtype"
t.datetime "voiddate", precision: nil
t.datetime "voiddate"
t.bigint "owning_organisation_id"
t.bigint "managing_organisation_id"
t.integer "renttype"
@ -459,6 +459,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_11_152702) do
t.boolean "show_on_unauthenticated_pages"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "show_additional_page"
end
create_table "organisation_relationships", force: :cascade do |t|
@ -779,8 +780,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_11_152702) do
t.string "name"
t.bigint "organisation_id"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at", precision: nil
t.datetime "last_sign_in_at", precision: nil
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "role"

1
spec/factories/notification.rb

@ -6,5 +6,6 @@ FactoryBot.define do
start_date { Time.zone.yesterday }
end_date { Time.zone.tomorrow }
show_on_unauthenticated_pages { false }
show_additional_page { true }
end
end

22
spec/features/notifications_page_spec.rb → spec/features/notifications_spec.rb

@ -1,7 +1,7 @@
require "rails_helper"
require_relative "form/helpers"
RSpec.describe "Notifications Page Features" do
RSpec.describe "Notifications Features" do
include Helpers
context "when there are notifications" do
@ -9,32 +9,30 @@ RSpec.describe "Notifications Page Features" do
context "when the notifications are currently active" do
before do
create(:notification, title: "Notification title 1")
create(:notification, title: "Notification title 2")
create(:notification, title: "Notification title 3")
create(:notification, start_date: Time.zone.yesterday, title: "Notification title 1")
create(:notification, start_date: Time.zone.yesterday, end_date: Time.zone.tomorrow, title: "Notification title 2")
sign_in user
visit(notifications_path)
visit(root_path)
end
it "does not show the notification banner" do
expect(page).not_to have_content("Notification 1 of")
expect(page).not_to have_link("Dismiss")
expect(page).not_to have_link("Link text")
it "shows the notification banner" do
expect(page).to have_content("Notification 1 of")
expect(page).to have_link("Dismiss")
end
end
context "when the notifications are not currently active" do
before do
create(:notification, end_date: Time.zone.yesterday, title: "Notification title 1")
create(:notification, start_date: Time.zone.yesterday, end_date: Time.zone.yesterday, title: "Notification title 1")
create(:notification, start_date: Time.zone.tomorrow, title: "Notification title 2")
create(:notification, start_date: nil, title: "Notification title 3")
sign_in user
visit(notifications_path)
visit(root_path)
end
it "does not show the notifications banner" do
expect(page).not_to have_content("Notification 1 of")
expect(page).not_to have_link("Dismiss")
expect(page).not_to have_link("Link text")
end
end
end

7
spec/features/start_page_spec.rb

@ -30,14 +30,15 @@ RSpec.describe "Start Page Features" do
end
context "when the unauthenticated user clicks a notification link" do
let!(:notification) { create(:notification, title: "Notification title", link_text: "link", page_content: "Some html content", show_on_unauthenticated_pages: true) }
before do
create(:notification, show_on_unauthenticated_pages: true)
visit(root_path)
click_link("Link text")
click_link("link")
end
it "takes them to the notification details page" do
expect(page).to have_current_path(notifications_path)
expect(page).to have_current_path(notification_path(notification))
expect(page).to have_content("Notification title")
expect(page).to have_content("Some html content")
expect(page).to have_link("Back to Start")

2
spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb

@ -25,7 +25,7 @@ RSpec.describe "bulk_update" do
allow(ENV).to receive(:[]).with("BULK_UPLOAD_BUCKET").and_return(instance_name)
WebMock.stub_request(:get, /api\.postcodes\.io/)
.to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})
.to_return(status: 404, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})
WebMock.stub_request(:get, /api\.postcodes\.io\/postcodes\/B11BB/)
.to_return(status: 200, body: '{"status":200,"result":{"admin_district":"Westminster","codes":{"admin_district":"E09000033"}}}', headers: {})
end

8
spec/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check_spec.rb

@ -33,7 +33,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCh
[
{
"age2_known" => 0,
"no_females_in_a_pregnant_household?" => true,
"all_male_tenants_in_a_pregnant_household?" => true,
},
],
)
@ -54,7 +54,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCh
it "has the correct informative_text" do
expect(page.informative_text).to eq({
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [
{
"key" => "sex1",
@ -78,7 +78,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCh
[
{
"age3_known" => 0,
"no_females_in_a_pregnant_household?" => true,
"all_male_tenants_in_a_pregnant_household?" => true,
},
],
)
@ -99,7 +99,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCh
it "has the correct informative_text" do
expect(page.informative_text).to eq({
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [
{
"key" => "sex1",

8
spec/models/form/lettings/pages/no_females_pregnant_household_person_value_check_spec.rb

@ -33,7 +33,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck
[
{
"details_known_2" => 0,
"no_females_in_a_pregnant_household?" => true,
"all_male_tenants_in_a_pregnant_household?" => true,
},
],
)
@ -54,7 +54,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck
it "has the correct informative_text" do
expect(page.informative_text).to eq({
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [
{
"key" => "sex1",
@ -78,7 +78,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck
[
{
"details_known_3" => 0,
"no_females_in_a_pregnant_household?" => true,
"all_male_tenants_in_a_pregnant_household?" => true,
},
],
)
@ -99,7 +99,7 @@ RSpec.describe Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck
it "has the correct informative_text" do
expect(page.informative_text).to eq({
"translation" => "soft_validations.pregnancy.no_females",
"translation" => "soft_validations.pregnancy.all_male_tenants",
"arguments" => [
{
"key" => "sex1",

28
spec/models/form/sales/questions/person_known_spec.rb

@ -35,6 +35,10 @@ RSpec.describe Form::Sales::Questions::PersonKnown, type: :model do
expect(question.hint_text).to be_nil
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to be_nil
end
context "with person 2" do
let(:question_id) { "details_known_2" }
let(:person_index) { 2 }
@ -51,12 +55,6 @@ RSpec.describe Form::Sales::Questions::PersonKnown, type: :model do
expect(question.check_answer_label).to eq("Details known for person 2?")
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq(
"depends_on" => [{ "details_known_2" => 1 }],
)
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
@ -78,12 +76,6 @@ RSpec.describe Form::Sales::Questions::PersonKnown, type: :model do
expect(question.check_answer_label).to eq("Details known for person 3?")
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq(
"depends_on" => [{ "details_known_3" => 1 }],
)
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(3)
end
@ -105,12 +97,6 @@ RSpec.describe Form::Sales::Questions::PersonKnown, type: :model do
expect(question.check_answer_label).to eq("Details known for person 4?")
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq(
"depends_on" => [{ "details_known_4" => 1 }],
)
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(4)
end
@ -132,12 +118,6 @@ RSpec.describe Form::Sales::Questions::PersonKnown, type: :model do
expect(question.check_answer_label).to eq("Details known for person 5?")
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq(
"depends_on" => [{ "details_known_5" => 1 }],
)
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(5)
end

4
spec/models/lettings_log_derived_fields_spec.rb

@ -981,9 +981,9 @@ RSpec.describe LettingsLog, type: :model do
end
describe "deriving voiddate from startdate" do
let(:startdate) { Time.zone.now }
let(:startdate) { Time.zone.now.beginning_of_day }
it "correctly derives voiddate if the letting is a renewal and clears it if it is not" do
it "correctly derives voiddate if the letting is a renewal" do
log.assign_attributes(renewal: 1, startdate:)
expect { log.set_derived_fields! }.to change(log, :voiddate).to startdate

11
spec/models/lettings_log_spec.rb

@ -329,17 +329,6 @@ RSpec.describe LettingsLog do
.to raise_error(ActiveRecord::RecordInvalid, /#{I18n.t("validations.postcode")}/)
end
context "when the local authority lookup times out" do
before do
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
end
it "logs a warning" do
expect(Rails.logger).to receive(:warn).with("Postcodes.io lookup timed out")
address_lettings_log.update!({ postcode_known: 1, postcode_full: "M1 1AD" })
end
end
it "correctly resets all fields if property postcode not known" do
address_lettings_log.update!({ postcode_known: 0 })

37
spec/models/notification_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Notification, type: :model do
describe "#valid?" do
context "when show additional page is true" do
context "and page_content is blank" do
let(:notification) { build(:notification, show_additional_page: true, page_content: "") }
it "adds an error to page_content" do
notification.valid?
expect(notification.errors[:page_content]).to include("Enter the page content")
end
end
context "and link_text is blank" do
let(:notification) { build(:notification, show_additional_page: true, link_text: nil) }
it "adds an error to link_text" do
notification.valid?
expect(notification.errors[:link_text]).to include("Enter the link text")
end
end
end
context "when show additional page is false" do
context "and page_content and link_text are blank" do
let(:notification) { build(:notification, show_additional_page: false, link_text: nil, page_content: nil) }
it "is valid" do
expect(notification).to be_valid
end
end
end
end
end

11
spec/models/sales_log_spec.rb

@ -628,17 +628,6 @@ RSpec.describe SalesLog, type: :model do
.to raise_error(ActiveRecord::RecordInvalid, /#{I18n.t("validations.postcode")}/)
end
context "when the local authority lookup times out" do
before do
allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
end
it "logs a warning" do
expect(Rails.logger).to receive(:warn).with("Postcodes.io lookup timed out")
address_sales_log.update!({ pcodenk: 1, postcode_full: "M1 1AD" })
end
end
it "correctly resets all fields if property postcode not known" do
address_sales_log.update!({ pcodenk: 1 })

12
spec/models/validations/soft_validations_spec.rb

@ -153,24 +153,24 @@ RSpec.describe Validations::SoftValidations do
end
describe "pregnancy soft validations" do
context "when there are no female tenants" do
context "when all tenants are male" do
it "shows the interruption screen" do
record.age1 = 43
record.sex1 = "M"
record.preg_occ = 1
record.hhmemb = 1
record.age1_known = 0
expect(record.no_females_in_a_pregnant_household?).to be true
expect(record.all_male_tenants_in_a_pregnant_household?).to be true
end
end
context "when there are no female tenants and age of other tenants is unknown" do
context "when there all tenants are male and age of tenants is unknown" do
it "shows the interruption screen" do
record.sex1 = "M"
record.preg_occ = 1
record.hhmemb = 1
record.age1_known = 1
expect(record.no_females_in_a_pregnant_household?).to be true
expect(record.all_male_tenants_in_a_pregnant_household?).to be true
end
end
@ -206,7 +206,7 @@ RSpec.describe Validations::SoftValidations do
record.sex1 = "F"
record.preg_occ = 1
record.hhmemb = 1
expect(record.no_females_in_a_pregnant_household?).to be false
expect(record.all_male_tenants_in_a_pregnant_household?).to be false
expect(record.female_in_pregnant_household_in_soft_validation_range?).to be false
end
end
@ -215,7 +215,7 @@ RSpec.describe Validations::SoftValidations do
it "does not show the interruption screen" do
record.preg_occ = 1
record.hhmemb = 2
expect(record.no_females_in_a_pregnant_household?).to be false
expect(record.all_male_tenants_in_a_pregnant_household?).to be false
expect(record.female_in_pregnant_household_in_soft_validation_range?).to be false
end
end

2
spec/request_helper.rb

@ -4,7 +4,7 @@ module RequestHelper
def self.stub_http_requests
WebMock.disable_net_connect!(allow_localhost: true)
WebMock.stub_request(:get, /api\.postcodes\.io/)
.to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})
.to_return(status: 404, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})
WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA")
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {})

163
spec/requests/notifications_controller_spec.rb

@ -0,0 +1,163 @@
require "rails_helper"
RSpec.describe NotificationsController, type: :request do
context "when user is signed in as a support user" do
let(:support_user) { create(:user, :support) }
before do
allow(support_user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in support_user
end
describe "#create" do
let(:request) { post notifications_path, params: params }
context "with valid parameters" do
let(:params) { { "notification": { title: "Test Create", show_on_unauthenticated_pages: "1", show_additional_page: "1", link_text: "link", page_content: "page" } } }
it "creates a new notification with no start date set" do
request
notification = Notification.find_by(title: "Test Create")
expect(notification.show_on_unauthenticated_pages).to be(true)
expect(notification.show_additional_page).to be(true)
expect(notification.link_text).to eq("link")
expect(notification.page_content).to eq("page")
expect(notification.start_date).to be_nil
end
it "redirects to check answers page" do
request
notification = Notification.find_by(title: "Test Create")
expect(response).to redirect_to(notification_check_answers_path(notification))
end
end
context "with invalid parameters" do
let(:params) { { "notification": { title: "", show_on_unauthenticated_pages: "1" } } }
it "gives an error response" do
request
expect(response).to have_http_status(:unprocessable_entity)
end
end
context "when show additional page is false" do
let(:params) { { "notification": { title: "No Additional Page", show_on_unauthenticated_pages: "1", show_additional_page: "0", link_text: "text", page_content: "content" } } }
it "ignores values for link_text and page_content" do
request
notification = Notification.find_by(title: "No Additional Page")
expect(notification.link_text).to be_nil
expect(notification.page_content).to be_nil
end
end
end
describe "#update" do
let(:notification) { create(:notification, title: "Initial Title", start_date: nil, end_date: nil) }
let(:request) { patch notification_path(notification), params: params }
context "when start_now is set to true" do
let(:params) { { "notification": { start_now: true } } }
it "sets the start date on the notification" do
request
notification.reload
expect(notification.start_date).not_to be_nil
expect(notification.start_date).to be < Time.zone.now
end
it "redirects to the home page" do
request
expect(response).to redirect_to(root_path)
end
end
context "when start_now is not set" do
let(:params) { { "notification": { title: "Updated Title", show_on_unauthenticated_pages: "1" } } }
it "sets the relevant values on the notification" do
request
notification.reload
expect(notification.title).to eql("Updated Title")
expect(notification.start_date).to be_nil
end
it "redirects to check answers" do
request
expect(response).to redirect_to(notification_check_answers_path(notification))
end
end
context "when show additional page is false" do
let(:notification) { create(:notification, show_additional_page: "0", link_text: "link", page_content: "page") }
let(:params) { { "notification": { show_additional_page: "0", link_text: "text", page_content: "content" } } }
it "removes values for link_text and page_content" do
request
notification.reload
expect(notification.link_text).to be_nil
expect(notification.page_content).to be_nil
end
end
end
describe "#delete" do
let(:notification) { create(:notification, end_date: nil) }
let(:request) { delete notification_delete_path(notification) }
it "sets end_date on the notification" do
request
notification.reload
expect(notification.end_date).to be < Time.zone.now
end
end
end
context "when user is signed in as a non-support user" do
let(:user) { create(:user, :data_coordinator) }
before do
sign_in user
end
describe "#create" do
let(:request) { post notifications_path, params: { "notification": { title: "Test Create" } } }
it "returns not found" do
request
expect(response).to have_http_status(:not_found)
end
it "does not create a notification" do
expect { request }.not_to change(Notification, :count)
end
end
describe "#update" do
let(:notification) { create(:notification) }
let(:request) { patch notification_path(notification), params: { "notification": { title: "Test Update" } } }
it "returns not found" do
request
expect(response).to have_http_status(:not_found)
end
end
describe "#delete" do
let(:notification) { create(:notification, end_date: nil) }
let(:request) { delete notification_delete_path(notification) }
it "returns not found" do
request
expect(response).to have_http_status(:not_found)
end
it "does not set end_date on the notification" do
request
notification.reload
expect(notification.end_date).to be_nil
end
end
end
end

77
spec/services/postcode_service_spec.rb

@ -1,9 +1,80 @@
require "rails_helper"
describe PostcodeService do
let(:postcode) { "s r81LS\u00A0" }
let(:service) { described_class.new }
it "returns clean postcode" do
expect(described_class.clean(postcode)).to eq "SR81LS"
describe "clean" do
let(:postcode) { "s r81LS\u00A0" }
it "returns clean postcode" do
expect(described_class.clean(postcode)).to eq "SR81LS"
end
end
describe "lookup" do
before do
Excon.defaults[:mock] = true
Excon.defaults[:stubs] = :local
end
context "when the request returns a success response" do
before do
Excon.stub({}, { body: '{"result": { "admin_district": "District", "codes": { "admin_district": "123" } } }', status: 200 })
end
it "returns the admin district and admin district code" do
result = service.lookup("A00 0AA")
expect(result[:location_code]).to eq("123")
expect(result[:location_admin_district]).to eq("District")
end
end
context "when the request returns a not found response" do
before do
Excon.stub({}, { status: 404 })
end
it "returns nil" do
result = service.lookup("A00 0AA")
expect(result).to be_nil
end
it "logs the error at warning level" do
expect(Rails.logger).to receive(:warn).with(match "404 Not Found")
service.lookup("A00 0AA")
end
end
context "when the request returns an error response" do
before do
Excon.stub({}, { body: "This is an error message that is not valid json", status: 500 })
end
it "returns nil" do
result = service.lookup("A00 0AA")
expect(result).to be_nil
end
it "logs the error at warning level" do
expect(Rails.logger).to receive(:warn).with(match "This is an error message that is not valid json")
service.lookup("A00 0AA")
end
end
context "when the request returns a success response that causes later errors" do
before do
Excon.stub({}, { body: '{"result": { "admin_district": "District" } }', status: 200 })
end
it "returns nil" do
result = service.lookup("A00 0AA")
expect(result).to be_nil
end
it "logs the error at error level" do
expect(Rails.logger).to receive(:error).with(match "Unexpected error with postcode lookup request")
service.lookup("A00 0AA")
end
end
end
end

92
yarn.lock

@ -1792,10 +1792,10 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
body-parser@1.20.2, body-parser@^1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
body-parser@1.20.3, body-parser@^1.20.2:
version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
@ -1805,7 +1805,7 @@ body-parser@1.20.2, body-parser@^1.20.2:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@ -2488,6 +2488,11 @@ encodeurl@~1.0.1, encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
engine.io-client@~6.5.2:
version "6.5.4"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.4.tgz#b8bc71ed3f25d0d51d587729262486b4b33bd0d0"
@ -2953,36 +2958,36 @@ express-session@^1.18.0:
uid-safe "~2.1.5"
express@^4.18.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
version "4.21.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915"
integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
finalhandler "1.3.1"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
path-to-regexp "0.1.10"
proxy-addr "~2.0.7"
qs "6.11.0"
qs "6.13.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
send "0.19.0"
serve-static "1.16.2"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@ -3106,13 +3111,13 @@ finalhandler@1.1.0:
statuses "~1.3.1"
unpipe "~1.0.0"
finalhandler@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
finalhandler@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
on-finished "2.4.1"
parseurl "~1.3.3"
@ -4224,10 +4229,10 @@ meow@^13.2.0:
resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f"
integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-descriptors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0:
version "2.0.0"
@ -4625,10 +4630,10 @@ path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-type@^4.0.0:
version "4.0.0"
@ -4802,14 +4807,7 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
qs@6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
dependencies:
side-channel "^1.0.4"
qs@^6.4.0:
qs@6.13.0, qs@^6.4.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
@ -5209,10 +5207,10 @@ send@0.16.2:
range-parser "~1.2.0"
statuses "~1.4.0"
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
send@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
@ -5258,15 +5256,15 @@ serve-static@1.13.2:
parseurl "~1.3.2"
send "0.16.2"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
serve-static@1.16.2:
version "1.16.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
dependencies:
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.18.0"
send "0.19.0"
server-destroy@1.0.1:
version "1.0.1"

Loading…
Cancel
Save