From 9c7c964783df48f63e13e9b387b507fc3bf78463 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:22:48 +0100 Subject: [PATCH 01/29] Filter out the primary client group option from secondary client group question (#809) Co-authored-by: Sam Collard Co-authored-by: Sam Collard --- app/views/schemes/secondary_client_group.html.erb | 2 +- spec/features/schemes_helpers.rb | 2 +- spec/requests/schemes_controller_spec.rb | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/schemes/secondary_client_group.html.erb b/app/views/schemes/secondary_client_group.html.erb index d229883ba..4f48dab00 100644 --- a/app/views/schemes/secondary_client_group.html.erb +++ b/app/views/schemes/secondary_client_group.html.erb @@ -14,7 +14,7 @@ <%= render partial: "organisations/headings", locals: { main: "What is the other client group?", sub: @scheme.service_name } %> - <% secondary_client_group_selection = Scheme.secondary_client_groups.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key) } %> + <% secondary_client_group_selection = Scheme.secondary_client_groups.keys.excluding("Missing", @scheme.primary_client_group).map { |key, _| OpenStruct.new(id: key, name: key) } %> <%= f.govuk_collection_radio_buttons :secondary_client_group, secondary_client_group_selection, :id, diff --git a/spec/features/schemes_helpers.rb b/spec/features/schemes_helpers.rb index d1a4db2dc..fdbec0c3a 100644 --- a/spec/features/schemes_helpers.rb +++ b/spec/features/schemes_helpers.rb @@ -44,7 +44,7 @@ module SchemesHelpers end def fill_in_and_save_secondary_client_group - choose "Homeless families with support needs" + choose "Offenders and people at risk of offending" click_button "Save and continue" end diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index ce6805f35..3ccc9e461 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -1334,7 +1334,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme) } + let!(:scheme) { FactoryBot.create(:scheme, primary_client_group: Scheme::PRIMARY_CLIENT_GROUP[:"Homeless families with support needs"]) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -1346,6 +1346,11 @@ RSpec.describe SchemesController, type: :request do expect(response).to have_http_status(:ok) expect(page).to have_content("What is the other client group?") end + + it "does not show the primary client group as an option" do + expect(scheme.primary_client_group).not_to be_nil + expect(page).not_to have_content("Homeless families with support needs") + end end end From bd0173fed068e2af20a8ae14a62a38c657cb9070 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:34:20 +0100 Subject: [PATCH 02/29] Cldc 1394 schemes cya (#797) * Mark scheme location as confirmed * Route to check your answers after any changes are made to the scheme. Display change for all editable fields * Add some functionality * Display banners and redirect to check answers after editing location name * Remove a test * update locations confirmation * reuse can_change_scheme_answer? * Use path helpers * Redirect to view scheme when trying to access create form pages for the scheme (cherry picked from commit 761bf97dc2719002730989380899a0ab1f115fd2) * add before action for confirmed schemes * update location path * lint Co-authored-by: Dushan Despotovic --- app/controllers/locations_controller.rb | 2 +- app/controllers/schemes_controller.rb | 19 ++- app/helpers/check_answers_helper.rb | 13 ++ app/views/locations/edit.html.erb | 4 +- .../schemes/_scheme_summary_list_row.html.erb | 4 +- app/views/schemes/check_answers.html.erb | 4 +- config/routes.rb | 1 + .../20220729110846_add_confirmed_location.rb | 7 + db/schema.rb | 1 + spec/features/schemes_spec.rb | 96 +++++++++++-- spec/requests/schemes_controller_spec.rb | 129 ++++++++++++++---- 11 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 db/migrate/20220729110846_add_confirmed_location.rb diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index de8a2d835..b88b895fb 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -56,7 +56,7 @@ class LocationsController < ApplicationController when "edit" location_params[:add_another_location] == "Yes" ? redirect_to(new_location_path(@location.scheme)) : redirect_to(scheme_check_answers_path(@scheme, anchor: "locations")) when "edit-name" - redirect_to(locations_path(@scheme)) + redirect_to(scheme_check_answers_path(@scheme, anchor: "locations")) end else render :edit, status: :unprocessable_entity diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index c10784c2d..d4168e355 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -5,9 +5,9 @@ class SchemesController < ApplicationController before_action :authenticate_user! before_action :find_resource, except: %i[index] before_action :authenticate_scope! + before_action :redirect_if_scheme_confirmed, only: %i[primary_client_group confirm_secondary_client_group secondary_client_group support details] def index - flash[:notice] = "#{Scheme.find(params[:scheme_id].to_i).service_name} has been created." if params[:scheme_id] redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support? all_schemes = Scheme.all.order("service_name ASC") @@ -52,10 +52,19 @@ class SchemesController < ApplicationController check_answers = params[:scheme][:check_answers] page = params[:scheme][:page] + scheme_previously_confirmed = @scheme.confirmed? validation_errors scheme_params if @scheme.errors.empty? && @scheme.update(scheme_params) - if check_answers + if scheme_params[:confirmed] == "true" + @scheme.locations.update!(confirmed: true) + flash[:notice] = if scheme_previously_confirmed + "#{@scheme.service_name} has been updated." + else + "#{@scheme.service_name} has been created." + end + redirect_to scheme_path(@scheme) + elsif check_answers if confirm_secondary_page? page redirect_to scheme_secondary_client_group_path(@scheme, check_answers: "true") else @@ -177,7 +186,7 @@ private scheme_details_path(@scheme) end when "edit-name" - scheme_path(@scheme) + scheme_check_answers_path(@scheme) when "check-answers" schemes_path(scheme_id: @scheme.id) end @@ -246,4 +255,8 @@ private render_not_found and return end end + + def redirect_if_scheme_confirmed + redirect_to @scheme if @scheme.confirmed? + end end diff --git a/app/helpers/check_answers_helper.rb b/app/helpers/check_answers_helper.rb index 982cab7cc..760c08ab0 100644 --- a/app/helpers/check_answers_helper.rb +++ b/app/helpers/check_answers_helper.rb @@ -11,6 +11,19 @@ module CheckAnswersHelper end end + def can_change_scheme_answer?(attribute_name, scheme) + editable_attributes = current_user.support? ? ["Name", "Confidential information", "Housing stock owned by"] : ["Name", "Confidential information"] + !scheme.confirmed? || editable_attributes.include?(attribute_name) + end + + def get_location_change_link_href(scheme, location) + if location.confirmed? + location_edit_name_path(id: scheme.id, location_id: location.id) + else + location_edit_path(id: scheme.id, location_id: location.id) + end + end + private def answered_questions_count(subsection, case_log, current_user) diff --git a/app/views/locations/edit.html.erb b/app/views/locations/edit.html.erb index 6fe2496b2..361e050ef 100644 --- a/app/views/locations/edit.html.erb +++ b/app/views/locations/edit.html.erb @@ -7,7 +7,9 @@ ) %> <% end %> -<%= form_for(@location, method: :patch, url: location_path) do |f| %> +<%= render partial: "organisations/headings", locals: { main: "Add a location to this scheme", sub: @scheme.service_name } %> + +<%= form_for(@location, method: :patch, url: location_path(@location)) do |f| %>
<%= f.govuk_error_summary %> diff --git a/app/views/schemes/_scheme_summary_list_row.html.erb b/app/views/schemes/_scheme_summary_list_row.html.erb index a02e5d25f..df8939df1 100644 --- a/app/views/schemes/_scheme_summary_list_row.html.erb +++ b/app/views/schemes/_scheme_summary_list_row.html.erb @@ -1,4 +1,4 @@ -
"> +
">
<%= attribute[:name].to_s %>
@@ -14,7 +14,7 @@ <%= details_html(attribute) %> <% end %> - <% if !scheme.confirmed? || attribute[:name] == "Name" %> + <% if can_change_scheme_answer?(attribute[:name], scheme) %>
Change
diff --git a/app/views/schemes/check_answers.html.erb b/app/views/schemes/check_answers.html.erb index 7319259f4..f2f13c7e1 100644 --- a/app/views/schemes/check_answers.html.erb +++ b/app/views/schemes/check_answers.html.erb @@ -10,7 +10,7 @@
<% @scheme.check_details_attributes.each do |attr| %> <% next if current_user.data_coordinator? && attr[:name] == ("owned by") %> - <%= render partial: "scheme_summary_list_row", locals: { scheme: @scheme, attribute: attr, change_link: scheme_details_path(@scheme, check_answers: true) } %> + <%= render partial: "scheme_summary_list_row", locals: { scheme: @scheme, attribute: attr, change_link: @scheme.confirmed? ? scheme_edit_name_path(@scheme) : scheme_details_path(@scheme, check_answers: true) } %> <% end %> <% if !@scheme.arrangement_type_same? %> <% @scheme.check_support_services_provider_attributes.each do |attr| %> @@ -68,7 +68,7 @@ <%= table.body do |body| %> <%= body.row do |row| %> <% row.cell(text: location.id) %> - <% row.cell(text: simple_format(location_cell(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> + <% row.cell(text: simple_format(location_cell(location, get_location_change_link_href(@scheme, location)), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> <% row.cell(text: location.units) %> <% row.cell(text: simple_format("#{location.type_of_unit}")) %> <% row.cell(text: location.mobility_type) %> diff --git a/config/routes.rb b/config/routes.rb index a6b0b9654..65130cd35 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -48,6 +48,7 @@ Rails.application.routes.draw do member do resources :locations do get "edit-name", to: "locations#edit_name" + get "edit", to: "locations#edit" end end end diff --git a/db/migrate/20220729110846_add_confirmed_location.rb b/db/migrate/20220729110846_add_confirmed_location.rb new file mode 100644 index 000000000..3656a999e --- /dev/null +++ b/db/migrate/20220729110846_add_confirmed_location.rb @@ -0,0 +1,7 @@ +class AddConfirmedLocation < ActiveRecord::Migration[7.0] + def change + change_table :locations, bulk: true do |t| + t.column :confirmed, :boolean + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 8ead19234..417f03725 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -250,6 +250,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_02_125711) do t.string "mobility_type" t.datetime "startdate", precision: nil t.string "location_admin_district" + t.boolean "confirmed" t.index ["old_id"], name: "index_locations_on_old_id", unique: true t.index ["scheme_id"], name: "index_locations_on_scheme_id" end diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 9792dfc42..2df56bb3d 100644 --- a/spec/features/schemes_spec.rb +++ b/spec/features/schemes_spec.rb @@ -284,13 +284,34 @@ RSpec.describe "Schemes scheme Features" do expect(page).not_to have_button("Create scheme") end + it "allows you to edit the newly added location" do + click_link "Locations" + expect(page).to have_link(nil, href: /edit/) + end + + context "when you click save" do + it "displays a updated banner" do + click_button "Save" + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + expect(page).to have_content("has been updated") + end + + it "does not let you edit the saved location" do + click_link "Locations" + expect(page).to have_link(nil, href: /edit(?!-name)/) + click_button "Save" + click_link "Locations" + expect(page).not_to have_link(nil, href: /edit(?!-name)/) + end + end + context "when you click to view the scheme details" do before do click_link("Scheme") end - it "does not let you change details other than the name" do - assert_selector "a", text: "Change", count: 1 + it "does not let you change details other than the name, confidential information and housing stock owner" do + assert_selector "a", text: "Change", count: 3 end end end @@ -451,6 +472,7 @@ RSpec.describe "Schemes scheme Features" do it "lets me check my answers after adding a location" do fill_in_and_save_location + expect(page).to have_current_path("/schemes/#{scheme.id}/check-answers") expect(page).to have_content "Check your changes before creating this scheme" end @@ -561,11 +583,12 @@ RSpec.describe "Schemes scheme Features" do end it "adds scheme to the list of schemes" do + expect(page).to have_content "#{scheme.service_name} has been created." + click_link "Schemes" expect(page).to have_content "Supported housing schemes" expect(page).to have_content scheme.id_to_display expect(page).to have_content scheme.service_name expect(page).to have_content scheme.owning_organisation.name - expect(page).to have_content "#{scheme.service_name} has been created." end end @@ -704,9 +727,17 @@ RSpec.describe "Schemes scheme Features" do click_button "Save changes" end - it "lets me see amended details on the show page" do + it "lets me see amended details on the check answers page" do expect(page).to have_content "FooBar" + expect(page).to have_current_path("/schemes/#{scheme.id}/check-answers") + assert_selector "a", text: "Change", count: 3 + end + + it "lets me save the scheme" do + click_button "Save" expect(page).to have_current_path("/schemes/#{scheme.id}") + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + expect(page).to have_content("has been updated") end end end @@ -748,10 +779,10 @@ RSpec.describe "Schemes scheme Features" do click_button "Save and continue" end - it "returns to locations page and shows the new name" do + it "returns to locations check your answers page and shows the new name" do expect(page).to have_content location.id expect(page).to have_content "NewName" - expect(page).to have_current_path("/schemes/#{scheme.id}/locations") + expect(page.current_url.split("/").last).to eq("check-answers#locations") end end end @@ -834,8 +865,8 @@ RSpec.describe "Schemes scheme Features" do click_link("Scheme") end - it "does not let you change details other than the name" do - assert_selector "a", text: "Change", count: 1 + it "does not let you change details other than the name, Confidential information and Housing stock owned by" do + assert_selector "a", text: "Change", count: 3 end end end @@ -846,6 +877,55 @@ RSpec.describe "Schemes scheme Features" do end end + context "when I am signed in as a data coordinator" do + let(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation_id: user.organisation_id) } + + before do + visit("/logs") + fill_in("user[email]", with: user.email) + fill_in("user[password]", with: user.password) + click_button("Sign in") + end + + context "when editing a scheme" do + context "when I visit schemes page" do + before do + visit("schemes") + end + + context "when I click to see individual scheme" do + let(:scheme) { schemes.first } + + before do + FactoryBot.create(:location, scheme:) + click_link(scheme.service_name) + end + + context "when I click to change scheme name" do + before do + click_link("Change", href: "/schemes/#{scheme.id}/edit-name", match: :first) + end + + context "when I edit details" do + before do + fill_in "Scheme name", with: "FooBar" + check "This scheme contains confidential information" + click_button "Save changes" + end + + it "lets me see amended details on the check answers page" do + expect(page).to have_content "FooBar" + expect(page).to have_current_path("/schemes/#{scheme.id}/check-answers") + expect(page).to have_link("Change", href: /schemes\/#{scheme.id}\/edit-name/, count: 2) + end + end + end + end + end + end + end + context "when selecting a scheme" do let!(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) } let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: user.organisation, managing_organisation: user.organisation, arrangement_type: "The same organisation that owns the housing stock") } diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 3ccc9e461..fe133253f 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -37,14 +37,6 @@ RSpec.describe SchemesController, type: :request do get "/schemes" end - context "when params scheme_id is present" do - it "shows a success banner" do - get "/schemes", params: { scheme_id: schemes.first.id } - follow_redirect! - expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") - end - end - it "redirects to the organisation schemes path" do follow_redirect! expect(path).to match("/organisations/#{user.organisation.id}/schemes") @@ -93,13 +85,6 @@ RSpec.describe SchemesController, type: :request do expect(CGI.unescape_html(response.body)).to match("#{schemes.count} total schemes") end - context "when params scheme_id is present" do - it "shows a success banner" do - get "/schemes", params: { scheme_id: schemes.first.id } - expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") - end - end - context "when paginating over 20 results" do let(:total_schemes_count) { Scheme.count } @@ -884,9 +869,11 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support" do let(:user) { FactoryBot.create(:user, :support) } - let(:scheme_to_update) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let(:scheme_to_update) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } + # let!(:location) { FactoryBot.create(:location, scheme: scheme_to_update) } before do + FactoryBot.create(:location, scheme: scheme_to_update) allow(user).to receive(:need_two_factor_authentication?).and_return(false) sign_in user patch "/schemes/#{scheme_to_update.id}", params: @@ -939,6 +926,21 @@ RSpec.describe SchemesController, type: :request do expect(scheme_to_update.reload.primary_client_group).to eq("Homeless families with support needs") end end + + context "when saving a scheme" do + let(:params) { { scheme: { page: "check-answers", confirmed: "true" } } } + + it "marks the scheme as confirmed" do + expect(scheme_to_update.reload.confirmed?).to eq(true) + end + + it "marks all the scheme locations as confirmed" do + expect(scheme_to_update.locations.count > 0).to eq(true) + scheme_to_update.locations.each do |location| + expect(location.confirmed?).to eq(true) + end + end + end end context "when updating primary client group" do @@ -1175,7 +1177,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } let!(:another_scheme) { FactoryBot.create(:scheme) } before do @@ -1202,7 +1204,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme) } + let!(:scheme) { FactoryBot.create(:scheme, confirmed: nil) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -1214,6 +1216,21 @@ RSpec.describe SchemesController, type: :request do expect(response).to have_http_status(:ok) expect(page).to have_content("What client group is this scheme intended for?") end + + context "and the scheme is confirmed" do + before do + scheme.update!(confirmed: true) + get "/schemes/#{scheme.id}/primary-client-group" + end + + it "redirects to a view scheme page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(path).to match("/schemes/#{scheme.id}") + expect(page).to have_content(scheme.service_name) + assert_select "a", text: /Change/, count: 3 + end + end end end @@ -1241,7 +1258,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } let!(:another_scheme) { FactoryBot.create(:scheme) } before do @@ -1268,7 +1285,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme) } + let!(:scheme) { FactoryBot.create(:scheme, confirmed: nil) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -1280,6 +1297,21 @@ RSpec.describe SchemesController, type: :request do expect(response).to have_http_status(:ok) expect(page).to have_content("Does this scheme provide for another client group?") end + + context "and the scheme is confirmed" do + before do + scheme.update!(confirmed: true) + get "/schemes/#{scheme.id}/confirm-secondary-client-group" + end + + it "redirects to a view scheme page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(path).to match("/schemes/#{scheme.id}") + expect(page).to have_content(scheme.service_name) + assert_select "a", text: /Change/, count: 3 + end + end end end @@ -1307,7 +1339,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } let!(:another_scheme) { FactoryBot.create(:scheme) } before do @@ -1334,7 +1366,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme, primary_client_group: Scheme::PRIMARY_CLIENT_GROUP[:"Homeless families with support needs"]) } + let!(:scheme) { FactoryBot.create(:scheme, confirmed: nil, primary_client_group: Scheme::PRIMARY_CLIENT_GROUP[:"Homeless families with support needs"]) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -1347,6 +1379,21 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What is the other client group?") end + context "and the scheme is confirmed" do + before do + scheme.update!(confirmed: true) + get "/schemes/#{scheme.id}/secondary-client-group" + end + + it "redirects to a view scheme page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(path).to match("/schemes/#{scheme.id}") + expect(page).to have_content(scheme.service_name) + assert_select "a", text: /Change/, count: 3 + end + end + it "does not show the primary client group as an option" do expect(scheme.primary_client_group).not_to be_nil expect(page).not_to have_content("Homeless families with support needs") @@ -1378,7 +1425,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } let!(:another_scheme) { FactoryBot.create(:scheme) } before do @@ -1401,11 +1448,26 @@ RSpec.describe SchemesController, type: :request do expect(response).to have_http_status(:not_found) end end + + context "and the scheme is confirmed" do + before do + scheme.update!(confirmed: true) + get "/schemes/#{scheme.id}/support" + end + + it "redirects to a view scheme page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(path).to match("/schemes/#{scheme.id}") + expect(page).to have_content(scheme.service_name) + assert_select "a", text: /Change/, count: 2 + end + end end context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme) } + let!(:scheme) { FactoryBot.create(:scheme, confirmed: nil) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -1510,7 +1572,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } - let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation, confirmed: nil) } let!(:another_scheme) { FactoryBot.create(:scheme) } before do @@ -1533,11 +1595,26 @@ RSpec.describe SchemesController, type: :request do expect(response).to have_http_status(:not_found) end end + + context "and the scheme is confirmed" do + before do + scheme.update!(confirmed: true) + get "/schemes/#{scheme.id}/details" + end + + it "redirects to a view scheme page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(path).to match("/schemes/#{scheme.id}") + expect(page).to have_content(scheme.service_name) + assert_select "a", text: /Change/, count: 2 + end + end end context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } - let!(:scheme) { FactoryBot.create(:scheme) } + let!(:scheme) { FactoryBot.create(:scheme, confirmed: nil) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) From dd1dd974abbc437342d6127bc1237a7a498a310e Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Wed, 3 Aug 2022 17:13:43 +0100 Subject: [PATCH 03/29] Performance (#810) * Setup subsection is always enabled * Cache setup subsection page generation * Cache setup section page question generation --- app/models/form/setup/pages/created_by.rb | 3 +-- app/models/form/setup/pages/location.rb | 3 +-- app/models/form/setup/pages/needs_type.rb | 3 +-- app/models/form/setup/pages/organisation.rb | 3 +-- app/models/form/setup/pages/property_reference.rb | 3 +-- app/models/form/setup/pages/renewal.rb | 3 +-- app/models/form/setup/pages/rent_type.rb | 3 +-- app/models/form/setup/pages/scheme.rb | 3 +-- app/models/form/setup/pages/tenancy_start_date.rb | 3 +-- app/models/form/setup/pages/tenant_code.rb | 3 +-- app/models/form/setup/subsections/setup.rb | 7 +++++-- 11 files changed, 15 insertions(+), 22 deletions(-) diff --git a/app/models/form/setup/pages/created_by.rb b/app/models/form/setup/pages/created_by.rb index a32741eab..edaa43fff 100644 --- a/app/models/form/setup/pages/created_by.rb +++ b/app/models/form/setup/pages/created_by.rb @@ -4,12 +4,11 @@ class Form::Setup::Pages::CreatedBy < ::Form::Page @id = "created_by" @header = "" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::CreatedById.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/location.rb b/app/models/form/setup/pages/location.rb index 5b22df634..4a86c9a9a 100644 --- a/app/models/form/setup/pages/location.rb +++ b/app/models/form/setup/pages/location.rb @@ -3,7 +3,6 @@ class Form::Setup::Pages::Location < ::Form::Page super("location", hsh, subsection) @header = "" @description = "" - @questions = questions @depends_on = [{ "supported_housing_schemes_enabled?" => true, "needstype" => 2, @@ -12,7 +11,7 @@ class Form::Setup::Pages::Location < ::Form::Page end def questions - [ + @questions ||= [ Form::Setup::Questions::LocationId.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/needs_type.rb b/app/models/form/setup/pages/needs_type.rb index f1c78e1f1..0db6ef1c2 100644 --- a/app/models/form/setup/pages/needs_type.rb +++ b/app/models/form/setup/pages/needs_type.rb @@ -4,13 +4,12 @@ class Form::Setup::Pages::NeedsType < ::Form::Page @id = "needs_type" @header = "" @description = "" - @questions = questions @depends_on = [{ "supported_housing_schemes_enabled?" => true }] @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::NeedsType.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/organisation.rb b/app/models/form/setup/pages/organisation.rb index 011d010ed..166bc2181 100644 --- a/app/models/form/setup/pages/organisation.rb +++ b/app/models/form/setup/pages/organisation.rb @@ -4,12 +4,11 @@ class Form::Setup::Pages::Organisation < ::Form::Page @id = "organisation" @header = "" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::OwningOrganisationId.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/property_reference.rb b/app/models/form/setup/pages/property_reference.rb index 0314816c8..14fafade2 100644 --- a/app/models/form/setup/pages/property_reference.rb +++ b/app/models/form/setup/pages/property_reference.rb @@ -4,12 +4,11 @@ class Form::Setup::Pages::PropertyReference < ::Form::Page @id = "property_reference" @header = "" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::PropertyReference.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/renewal.rb b/app/models/form/setup/pages/renewal.rb index 959a1d94f..8a627d7c9 100644 --- a/app/models/form/setup/pages/renewal.rb +++ b/app/models/form/setup/pages/renewal.rb @@ -4,12 +4,11 @@ class Form::Setup::Pages::Renewal < ::Form::Page @id = "renewal" @header = "" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::Renewal.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/rent_type.rb b/app/models/form/setup/pages/rent_type.rb index 317fa0e0c..9c927c9af 100644 --- a/app/models/form/setup/pages/rent_type.rb +++ b/app/models/form/setup/pages/rent_type.rb @@ -3,13 +3,12 @@ class Form::Setup::Pages::RentType < ::Form::Page super("rent_type", hsh, subsection) @header = "" @description = "" - @questions = questions @depends_on = [{ "supported_housing_schemes_enabled?" => true }] @derived = true end def questions - [ + @questions ||= [ Form::Setup::Questions::RentType.new(nil, nil, self), Form::Setup::Questions::IrproductOther.new(nil, nil, self), ] diff --git a/app/models/form/setup/pages/scheme.rb b/app/models/form/setup/pages/scheme.rb index b60d07964..2b9cc5dbf 100644 --- a/app/models/form/setup/pages/scheme.rb +++ b/app/models/form/setup/pages/scheme.rb @@ -3,7 +3,6 @@ class Form::Setup::Pages::Scheme < ::Form::Page super("scheme", hsh, subsection) @header = "" @description = "" - @questions = questions @depends_on = [{ "supported_housing_schemes_enabled?" => true, "needstype" => 2, @@ -11,7 +10,7 @@ class Form::Setup::Pages::Scheme < ::Form::Page end def questions - [ + @questions ||= [ Form::Setup::Questions::SchemeId.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/tenancy_start_date.rb b/app/models/form/setup/pages/tenancy_start_date.rb index be1f24ca9..117ef8452 100644 --- a/app/models/form/setup/pages/tenancy_start_date.rb +++ b/app/models/form/setup/pages/tenancy_start_date.rb @@ -3,12 +3,11 @@ class Form::Setup::Pages::TenancyStartDate < ::Form::Page super @id = "tenancy_start_date" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::TenancyStartDate.new(nil, nil, self), ] end diff --git a/app/models/form/setup/pages/tenant_code.rb b/app/models/form/setup/pages/tenant_code.rb index aa2b7e5c9..f7b8350d1 100644 --- a/app/models/form/setup/pages/tenant_code.rb +++ b/app/models/form/setup/pages/tenant_code.rb @@ -4,12 +4,11 @@ class Form::Setup::Pages::TenantCode < ::Form::Page @id = "tenant_code" @header = "" @description = "" - @questions = questions @subsection = subsection end def questions - [ + @questions ||= [ Form::Setup::Questions::TenantCode.new(nil, nil, self), ] end diff --git a/app/models/form/setup/subsections/setup.rb b/app/models/form/setup/subsections/setup.rb index 0f481af6b..1db76986d 100644 --- a/app/models/form/setup/subsections/setup.rb +++ b/app/models/form/setup/subsections/setup.rb @@ -3,12 +3,11 @@ class Form::Subsections::Setup < ::Form::Subsection super @id = "setup" @label = "Set up this lettings log" - @pages = [pages] @section = section end def pages - [ + @pages ||= [ Form::Setup::Pages::Organisation.new(nil, nil, self), Form::Setup::Pages::CreatedBy.new(nil, nil, self), Form::Setup::Pages::NeedsType.new(nil, nil, self), @@ -26,6 +25,10 @@ class Form::Subsections::Setup < ::Form::Subsection questions.select { |q| support_only_questions.include?(q.id) } + super end + def enabled?(_case_log) + true + end + private def support_only_questions From 19ee8b13b2c6b83d112e0f4f78cec4cd0db9346e Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 4 Aug 2022 09:03:14 +0100 Subject: [PATCH 04/29] Remove a redundant heading (#811) --- app/views/locations/edit.html.erb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/locations/edit.html.erb b/app/views/locations/edit.html.erb index 361e050ef..f4b74caf3 100644 --- a/app/views/locations/edit.html.erb +++ b/app/views/locations/edit.html.erb @@ -7,8 +7,6 @@ ) %> <% end %> -<%= render partial: "organisations/headings", locals: { main: "Add a location to this scheme", sub: @scheme.service_name } %> - <%= form_for(@location, method: :patch, url: location_path(@location)) do |f| %>
From f75acdd77dd672cf3044faae4b3922d15ee86e02 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Thu, 4 Aug 2022 09:21:12 +0100 Subject: [PATCH 05/29] Fix submission deadline text (#808) --- app/views/case_logs/edit.html.erb | 2 +- app/views/form/review.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/case_logs/edit.html.erb b/app/views/case_logs/edit.html.erb index dd5b6b18f..90ba5cd1b 100644 --- a/app/views/case_logs/edit.html.erb +++ b/app/views/case_logs/edit.html.erb @@ -29,7 +29,7 @@ <%= status_tag(@case_log.status) %>

- You can <%= govuk_link_to "review and make changes to this log", "/logs/#{@case_log.id}/review" %> up to 3 months after the end of the current collection year, which closes on 31 March <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>. + You can <%= govuk_link_to "review and make changes to this log", "/logs/#{@case_log.id}/review" %> until 2nd June <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>.

<% end %> <%= render "tasklist" %> diff --git a/app/views/form/review.html.erb b/app/views/form/review.html.erb index e8ff06a1f..3c8777359 100644 --- a/app/views/form/review.html.erb +++ b/app/views/form/review.html.erb @@ -11,7 +11,7 @@ <%= content_for(:title) %>

- You can review and make changes to this log up to 3 months after the end of the current collection year, which closes on 31 March <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>. + You can review and make changes to this log until 2nd June <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>.

<% @case_log.form.sections.map do |section| %>

<%= section.label %>

From 94d2a397795c8e1e5ed1a83b0763b8b8a61dfed9 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 4 Aug 2022 09:32:40 +0100 Subject: [PATCH 06/29] Back button on edit location page routes to check answers for locations (#812) * Back button on edit location page routes to check answers for locations * Update path Co-authored-by: James Rose Co-authored-by: James Rose --- app/views/locations/edit.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/locations/edit.html.erb b/app/views/locations/edit.html.erb index f4b74caf3..6e14503ed 100644 --- a/app/views/locations/edit.html.erb +++ b/app/views/locations/edit.html.erb @@ -3,7 +3,7 @@ <% content_for :before_content do %> <%= govuk_back_link( text: "Back", - href: "/schemes/#{@scheme.id}/support", + href: scheme_check_answers_path(@scheme, anchor: "locations"), ) %> <% end %> From a996deb1cb2cb137937e6994ac62c1d63cb401b1 Mon Sep 17 00:00:00 2001 From: Ted-U <92022120+Ted-U@users.noreply.github.com> Date: Thu, 4 Aug 2022 12:31:35 +0100 Subject: [PATCH 07/29] CLDC-1315: Model deletion cascade and foreign key constraints (#765) * failed rebase, copied onto new branch * lint * fix migration file and lint * fix migration - remove duplication * fix migration - remove duplicate foreign key * fix migration - remove duplication * lint * migration fix * fix migration and lint * keep foreign key for managing organisations --- app/models/organisation.rb | 6 ++-- app/models/scheme.rb | 4 +-- app/models/user.rb | 2 +- ...14646_add_delete_cascade_from_orgs_down.rb | 6 ++++ ...0220722133738_add_delete_caselogs_users.rb | 6 ++++ db/schema.rb | 5 +-- spec/features/organisation_spec.rb | 33 +++++++++++++++++++ 7 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20220720214646_add_delete_cascade_from_orgs_down.rb create mode 100644 db/migrate/20220722133738_add_delete_caselogs_users.rb diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 3cae243c2..7970c7c37 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -1,10 +1,10 @@ class Organisation < ApplicationRecord - has_many :users - has_many :owned_case_logs, class_name: "CaseLog", foreign_key: "owning_organisation_id" + has_many :users, dependent: :delete_all + has_many :owned_case_logs, class_name: "CaseLog", foreign_key: "owning_organisation_id", dependent: :delete_all has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id" has_many :data_protection_confirmations has_many :organisation_rent_periods - has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id" + has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all has_many :managed_schemes, class_name: "Scheme", foreign_key: "managing_organisation_id" scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } diff --git a/app/models/scheme.rb b/app/models/scheme.rb index e899a31d4..727388fae 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -1,8 +1,8 @@ class Scheme < ApplicationRecord belongs_to :owning_organisation, class_name: "Organisation" belongs_to :managing_organisation, optional: true, class_name: "Organisation" - has_many :locations - has_many :case_logs + has_many :locations, dependent: :delete_all + has_many :case_logs, dependent: :delete_all has_paper_trail diff --git a/app/models/user.rb b/app/models/user.rb index a664acc8d..ee1a2fb5f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,7 +6,7 @@ class User < ApplicationRecord :trackable, :lockable, :two_factor_authenticatable, :confirmable, :timeoutable belongs_to :organisation - has_many :owned_case_logs, through: :organisation + has_many :owned_case_logs, through: :organisation, dependent: :delete_all has_many :managed_case_logs, through: :organisation validate :validate_email diff --git a/db/migrate/20220720214646_add_delete_cascade_from_orgs_down.rb b/db/migrate/20220720214646_add_delete_cascade_from_orgs_down.rb new file mode 100644 index 000000000..21a73077a --- /dev/null +++ b/db/migrate/20220720214646_add_delete_cascade_from_orgs_down.rb @@ -0,0 +1,6 @@ +class AddDeleteCascadeFromOrgsDown < ActiveRecord::Migration[7.0] + def change + remove_foreign_key :schemes, :organisations, column: "owning_organisation_id" + add_foreign_key :schemes, :organisations, column: "owning_organisation_id", on_delete: :cascade + end +end diff --git a/db/migrate/20220722133738_add_delete_caselogs_users.rb b/db/migrate/20220722133738_add_delete_caselogs_users.rb new file mode 100644 index 000000000..5f3d3318f --- /dev/null +++ b/db/migrate/20220722133738_add_delete_caselogs_users.rb @@ -0,0 +1,6 @@ +class AddDeleteCaselogsUsers < ActiveRecord::Migration[7.0] + def change + add_foreign_key :case_logs, :organisations, column: "owning_organisation_id", on_delete: :cascade + add_foreign_key :users, :organisations, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 417f03725..aa7304022 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -380,8 +380,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_02_125711) do end add_foreign_key "case_logs", "locations" + add_foreign_key "case_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "case_logs", "schemes" add_foreign_key "locations", "schemes" - add_foreign_key "schemes", "organisations", column: "managing_organisation_id" - add_foreign_key "schemes", "organisations", column: "owning_organisation_id" + add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade + add_foreign_key "users", "organisations", on_delete: :cascade end diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb index 118638d84..ee7ec0c37 100644 --- a/spec/features/organisation_spec.rb +++ b/spec/features/organisation_spec.rb @@ -218,6 +218,39 @@ RSpec.describe "User Features" do end end end + + describe "delete cascade" do + context "when the organisation is deleted" do + let!(:organisation) { FactoryBot.create(:organisation) } + let!(:user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now, organisation:) } + let!(:scheme_to_delete) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:log_to_delete) { FactoryBot.create(:case_log, owning_organisation: user.organisation) } + + context "when organisation is deleted" do + it "child relationships ie logs, schemes and users are deleted too - application" do + organisation.destroy! + expect { organisation.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { CaseLog.find(log_to_delete.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { Scheme.find(scheme_to_delete.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + context "when the organisation is deleted" do + let!(:organisation) { FactoryBot.create(:organisation) } + let!(:user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now, organisation:) } + let!(:scheme_to_delete) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:log_to_delete) { FactoryBot.create(:case_log, :in_progress, needstype: 1, owning_organisation: user.organisation) } + + it "child relationships ie logs, schemes and users are deleted too - database" do + ActiveRecord::Base.connection.exec_query("DELETE FROM organisations WHERE id = #{organisation.id};") + expect { CaseLog.find(log_to_delete.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { Scheme.find(scheme_to_delete.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + end + end end end end From 563bfe1d95ed09a2eb4962dd507bca0e4fc967e2 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 4 Aug 2022 16:06:39 +0100 Subject: [PATCH 08/29] Cldc 1424 display postcode question (#813) * Reset wchair when needstype changes from supported housing to not supported housing Co-authored-by: James Rose * remove location_id if scheme needstype changes to general needs * only hide postcode known question from check your answers if it's answered Co-authored-by: James Rose --- app/models/case_log.rb | 4 +++- config/forms/2021_2022.json | 27 +++++++++++++++++++++++---- config/forms/2022_2023.json | 9 ++++++++- spec/models/case_log_spec.rb | 30 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 0e9d550e0..9b703a510 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -536,7 +536,9 @@ private dependent_questions = { waityear: [{ key: :renewal, value: 0 }], homeless: [{ key: :renewal, value: 0 }], referral: [{ key: :renewal, value: 0 }], - underoccupation_benefitcap: [{ key: :renewal, value: 0 }] } + underoccupation_benefitcap: [{ key: :renewal, value: 0 }], + wchair: [{ key: :needstype, value: 1 }], + location_id: [{ key: :needstype, value: 1 }] } dependent_questions.each do |dependent, conditions| condition_key = conditions.first[:key] diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index 7765ce9ba..a81f08b9f 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -34,7 +34,14 @@ "conditional_for": { "postcode_full": [1] }, - "hidden_in_check_answers": true + "hidden_in_check_answers": { + "depends_on": [{ + "postcode_known": 0 + }, + { + "postcode_known": 1 + }] + } }, "postcode_full": { "check_answer_label": "Postcode", @@ -1122,7 +1129,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ @@ -1248,7 +1259,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_age_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ @@ -1356,7 +1371,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 3890b1dbf..d7c903b25 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -34,7 +34,14 @@ "conditional_for": { "postcode_full": [1] }, - "hidden_in_check_answers": true + "hidden_in_check_answers": { + "depends_on": [{ + "postcode_known": 0 + }, + { + "postcode_known": 1 + }] + } }, "postcode_full": { "check_answer_label": "Postcode", diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index 712e1eddf..9520feffe 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -1842,6 +1842,36 @@ RSpec.describe CaseLog do end end + context "when it changes from a supported housing to not a supported housing" do + let(:location) { FactoryBot.create(:location, mobility_type: "A", postcode: "SW1P 4DG") } + let(:case_log) { FactoryBot.create(:case_log, location:) } + + it "resets inferred wchair value" do + case_log.update!({ needstype: 2 }) + + record_from_db = ActiveRecord::Base.connection.execute("select wchair from case_logs where id=#{case_log.id}").to_a[0] + expect(record_from_db["wchair"]).to eq(2) + expect(case_log["wchair"]).to eq(2) + + case_log.update!({ needstype: 1 }) + record_from_db = ActiveRecord::Base.connection.execute("select needstype from case_logs where id=#{case_log.id}").to_a[0] + expect(record_from_db["wchair"]).to eq(nil) + expect(case_log["wchair"]).to eq(nil) + end + + it "resets location" do + case_log.update!({ needstype: 2 }) + + record_from_db = ActiveRecord::Base.connection.execute("select location_id from case_logs where id=#{case_log.id}").to_a[0] + expect(record_from_db["location_id"]).to eq(location.id) + expect(case_log["location_id"]).to eq(location.id) + case_log.update!({ needstype: 1 }) + record_from_db = ActiveRecord::Base.connection.execute("select location_id from case_logs where id=#{case_log.id}").to_a[0] + expect(record_from_db["location_id"]).to eq(nil) + expect(case_log["location_id"]).to eq(nil) + end + end + context "when it is not a renewal" do let(:case_log) { FactoryBot.create(:case_log) } From 6fb8e44393a5a246c626acaeaf02b2455425dee8 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Fri, 5 Aug 2022 11:35:18 +0100 Subject: [PATCH 09/29] Add registration number to organisation details views (#815) * Add registration number to organisation details views * Downcase data-qa value --- app/models/organisation.rb | 17 +++++++++-------- app/views/organisations/show.html.erb | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 7970c7c37..609aa3cbf 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -63,15 +63,16 @@ class Organisation < ApplicationRecord def display_attributes [ - { name: "name", value: name, editable: true }, - { name: "address", value: address_string, editable: true }, - { name: "telephone_number", value: phone, editable: true }, - { name: "type", value: display_provider_type, editable: false }, - { name: "rent_periods", value: rent_period_labels, editable: false, format: :bullet }, + { name: "Name", value: name, editable: true }, + { name: "Address", value: address_string, editable: true }, + { name: "Telephone_number", value: phone, editable: true }, + { name: "Type of provider", value: display_provider_type, editable: false }, + { name: "Registration number", value: housing_registration_no || "", editable: false }, + { name: "Rent_periods", value: rent_period_labels, editable: false, format: :bullet }, { name: "Owns housing stock", value: holds_own_stock ? "Yes" : "No", editable: false }, - { name: "other_stock_owners", value: other_stock_owners, editable: false }, - { name: "managing_agents", value: managing_agents, editable: false }, - { name: "data_protection_agreement", value: data_protection_agreement_string, editable: false }, + { name: "Other stock owners", value: other_stock_owners, editable: false }, + { name: "Managing agents", value: managing_agents, editable: false }, + { name: "Data protection agreement", value: data_protection_agreement_string, editable: false }, ] end end diff --git a/app/views/organisations/show.html.erb b/app/views/organisations/show.html.erb index 3227e7b88..5cd8b64e0 100644 --- a/app/views/organisations/show.html.erb +++ b/app/views/organisations/show.html.erb @@ -22,7 +22,7 @@ <% row.action( visually_hidden_text: attr[:name].to_s.humanize.downcase, href: edit_organisation_path, - html_attributes: { "data-qa": "change-#{attr[:name]}" }, + html_attributes: { "data-qa": "change-#{attr[:name].downcase}" }, ) %> <% end %> <% else %> From 89120548bb2835cd7b2b89db7d8c9e2a9184a717 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Fri, 5 Aug 2022 11:44:44 +0100 Subject: [PATCH 10/29] Only consider active locations for routing (#814) * Only consider active locations for routing * Spec nil case --- .../derived_variables/case_log_variables.rb | 4 +-- app/models/location.rb | 2 ++ spec/factories/location.rb | 1 + spec/features/form/page_routing_spec.rb | 33 +++++++++++++++++++ spec/models/location_spec.rb | 18 ++++++++-- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/app/models/derived_variables/case_log_variables.rb b/app/models/derived_variables/case_log_variables.rb index edbece2a1..ea4df8009 100644 --- a/app/models/derived_variables/case_log_variables.rb +++ b/app/models/derived_variables/case_log_variables.rb @@ -8,7 +8,7 @@ module DerivedVariables::CaseLogVariables def scheme_has_multiple_locations? return false unless scheme - @scheme_locations_count ||= scheme.locations.size + @scheme_locations_count ||= scheme.locations.active.size @scheme_locations_count > 1 end @@ -188,7 +188,7 @@ private def reset_scheme_location! self.location = nil - if scheme && scheme.locations.size == 1 + if scheme && scheme.locations.active.size == 1 self.location = scheme.locations.first end end diff --git a/app/models/location.rb b/app/models/location.rb index c6f727f5c..fd2dde9fe 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -12,6 +12,8 @@ class Location < ApplicationRecord scope :search_by_postcode, ->(postcode) { where("REPLACE(postcode, ' ', '') ILIKE ?", "%#{postcode.delete(' ')}%") } scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by, ->(param) { search_by_name(param).or(search_by_postcode(param)) } + scope :started, -> { where("startdate <= ?", Time.zone.today).or(where(startdate: nil)) } + scope :active, -> { where(confirmed: true).and(started) } MOBILITY_TYPE = { "Wheelchair-user standard": "W", diff --git a/spec/factories/location.rb b/spec/factories/location.rb index 433eedd6f..a8418f8ba 100644 --- a/spec/factories/location.rb +++ b/spec/factories/location.rb @@ -8,6 +8,7 @@ FactoryBot.define do location_code { "E09000033" } location_admin_district { "Westminster" } startdate { Faker::Date.between(from: 6.months.ago, to: Time.zone.today) } + confirmed { true } scheme trait :export do postcode { "SW1A 2AA" } diff --git a/spec/features/form/page_routing_spec.rb b/spec/features/form/page_routing_spec.rb index abf65dad3..f3e6b39a5 100644 --- a/spec/features/form/page_routing_spec.rb +++ b/spec/features/form/page_routing_spec.rb @@ -111,4 +111,37 @@ RSpec.describe "Form Page Routing" do expect(find_field("case_log[startdate(1i)]").value).to eq(nil) end end + + context "when completing the setup section" do + context "with a supported housing log" do + let(:case_log) do + FactoryBot.create( + :case_log, + owning_organisation: user.organisation, + managing_organisation: user.organisation, + needstype: 2, + ) + end + + context "with a scheme with only 1 active location" do + let(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let!(:active_location) { FactoryBot.create(:location, scheme:) } + + before do + FactoryBot.create(:location, scheme:, startdate: Time.zone.today + 20.days) + visit("/logs/#{case_log.id}/scheme") + select(scheme.service_name, from: "case_log[scheme_id]") + click_button("Save and continue") + end + + it "does not route to the scheme location question" do + expect(page).to have_current_path("/logs/#{case_log.id}/renewal") + end + + it "infers the scheme location" do + expect(case_log.reload.location_id).to eq(active_location.id) + end + end + end + end end diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index dccc4674b..0b932b915 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -72,8 +72,10 @@ RSpec.describe Location, type: :model do describe "scopes" do before do - FactoryBot.create(:location, name: "ABC", postcode: "NW1 8RR") - FactoryBot.create(:location, name: "XYZ", postcode: "SE1 6HJ") + FactoryBot.create(:location, name: "ABC", postcode: "NW1 8RR", startdate: Time.zone.today) + FactoryBot.create(:location, name: "XYZ", postcode: "SE1 6HJ", startdate: Time.zone.today + 1.day) + FactoryBot.create(:location, name: "GHQ", postcode: "EW1 7JK", startdate: Time.zone.today - 1.day, confirmed: false) + FactoryBot.create(:location, name: "GHQ", postcode: "EW1 7JK", startdate: nil) end context "when searching by name" do @@ -96,5 +98,17 @@ RSpec.describe Location, type: :model do expect(described_class.search_by("nw18rr").count).to eq(1) end end + + context "when filtering by started locations" do + it "returns only locations that started today or earlier" do + expect(described_class.started.count).to eq(3) + end + end + + context "when filtering by active locations" do + it "returns only locations that started today or earlier and have been confirmed" do + expect(described_class.active.count).to eq(2) + end + end end end From d2e41bf8d73750e189517cb85b9c6607d6ca4e3c Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 5 Aug 2022 16:16:28 +0100 Subject: [PATCH 11/29] Strip whitespaces from names before saving organisation, scheme or a user (#816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Stip whitespaces from names before saving organisation, scheme or a user * Abstract strip whitespaces method and use it for locations too * refactor * lint * Use a gem 🙃 * sort gems alphabetically --- Gemfile | 1 + Gemfile.lock | 3 +++ app/models/location.rb | 2 ++ app/models/organisation.rb | 2 ++ app/models/scheme.rb | 2 ++ app/models/user.rb | 2 ++ spec/requests/locations_controller_spec.rb | 2 +- spec/requests/organisations_controller_spec.rb | 4 ++-- spec/requests/schemes_controller_spec.rb | 2 +- spec/requests/users_controller_spec.rb | 12 ++++++++++-- 10 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 168ace7af..6586ed800 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.2" +gem "auto_strip_attributes" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem "rails", "~> 7.0.2" # Use postgresql as the database for Active Record diff --git a/Gemfile.lock b/Gemfile.lock index 4bcfa6266..2239fcdf5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,6 +81,8 @@ GEM addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) ast (2.4.2) + auto_strip_attributes (2.6.0) + activerecord (>= 4.0) aws-eventstream (1.2.0) aws-partitions (1.609.0) aws-sdk-core (3.131.3) @@ -428,6 +430,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + auto_strip_attributes aws-sdk-s3 bootsnap (>= 1.4.4) bundler-audit diff --git a/app/models/location.rb b/app/models/location.rb index fd2dde9fe..c89ff0236 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -7,6 +7,8 @@ class Location < ApplicationRecord before_save :lookup_postcode!, if: :postcode_changed? + auto_strip_attributes :name + attr_accessor :add_another_location scope :search_by_postcode, ->(postcode) { where("REPLACE(postcode, ' ', '') ILIKE ?", "%#{postcode.delete(' ')}%") } diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 609aa3cbf..625c0bd05 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -12,6 +12,8 @@ class Organisation < ApplicationRecord has_paper_trail + auto_strip_attributes :name + PROVIDER_TYPE = { LA: 1, PRP: 2, diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 727388fae..45f09d4a5 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -19,6 +19,8 @@ class Scheme < ApplicationRecord validate :validate_confirmed + auto_strip_attributes :service_name + SENSITIVE = { No: 0, Yes: 1, diff --git a/app/models/user.rb b/app/models/user.rb index ee1a2fb5f..f9ed4a0e9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,8 @@ class User < ApplicationRecord has_one_time_password(encrypted: true) + auto_strip_attributes :name + ROLES = { data_provider: 1, data_coordinator: 2, diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index ed2132809..080260128 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -269,7 +269,7 @@ RSpec.describe LocationsController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } let!(:scheme) { FactoryBot.create(:scheme) } - let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", add_another_location: "No", postcode: "ZZ1 1ZZ", mobility_type: "N" } } } + let(:params) { { location: { name: " Test", units: "5", type_of_unit: "Bungalow", add_another_location: "No", postcode: "ZZ1 1ZZ", mobility_type: "N" } } } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 0498209f9..c467cd585 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -962,7 +962,7 @@ RSpec.describe OrganisationsController, type: :request do end describe "#create" do - let(:name) { "Unique new org name" } + let(:name) { " Unique new org name" } let(:address_line1) { "12 Random Street" } let(:address_line2) { "Manchester" } let(:postcode) { "MD1 5TR" } @@ -993,7 +993,7 @@ RSpec.describe OrganisationsController, type: :request do it "sets the organisation attributes correctly" do request organisation = Organisation.find_by(housing_registration_no:) - expect(organisation.name).to eq(name) + expect(organisation.name).to eq("Unique new org name") expect(organisation.address_line1).to eq(address_line1) expect(organisation.address_line2).to eq(address_line2) expect(organisation.postcode).to eq(postcode) diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index fe133253f..a5c0b6e4f 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -346,7 +346,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } let(:params) do - { scheme: { service_name: "testy", + { scheme: { service_name: " testy ", sensitive: "1", scheme_type: "Foyer", registered_under_care_act: "No", diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index a99d1e6c3..c5303776a 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -841,7 +841,7 @@ RSpec.describe UsersController, type: :request do let(:params) do { "user": { - name: "new user", + name: "new user ", email: "new_user@example.com", role: "data_coordinator", }, @@ -850,7 +850,7 @@ RSpec.describe UsersController, type: :request do let(:personalisation) do { - name: params[:user][:name], + name: "new user", email: params[:user][:email], organisation: user.organisation.name, link: include("/account/confirmation?confirmation_token="), @@ -871,6 +871,14 @@ RSpec.describe UsersController, type: :request do request end + it "creates a new scheme for user organisation with valid params" do + request + + expect(User.last.name).to eq("new user") + expect(User.last.email).to eq("new_user@example.com") + expect(User.last.role).to eq("data_coordinator") + end + it "redirects back to organisation users page" do request expect(response).to redirect_to("/organisations/#{user.organisation.id}/users") From 0e01918c623352e581638031803729b027a8d0eb Mon Sep 17 00:00:00 2001 From: baarkerlounger Date: Fri, 5 Aug 2022 16:22:14 +0100 Subject: [PATCH 12/29] Bump dependencies --- Gemfile | 3 +- Gemfile.lock | 33 ++--- yarn.lock | 402 +++++++++++++++++++++++++++++---------------------- 3 files changed, 244 insertions(+), 194 deletions(-) diff --git a/Gemfile b/Gemfile index 6586ed800..ab7d71ad3 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.2" -gem "auto_strip_attributes" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' gem "rails", "~> 7.0.2" # Use postgresql as the database for Active Record @@ -57,6 +56,8 @@ gem "sentry-rails" gem "sentry-ruby" # Possessives in strings gem "possessive" +# Strip whitespace from active record attributes +gem "auto_strip_attributes" group :development, :test do # Check gems for known vulnerabilities diff --git a/Gemfile.lock b/Gemfile.lock index 2239fcdf5..4eab78078 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -84,8 +84,8 @@ GEM auto_strip_attributes (2.6.0) activerecord (>= 4.0) aws-eventstream (1.2.0) - aws-partitions (1.609.0) - aws-sdk-core (3.131.3) + aws-partitions (1.615.0) + aws-sdk-core (3.131.6) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) @@ -109,7 +109,7 @@ GEM parser (>= 2.4) smart_properties bindex (0.8.1) - bootsnap (1.12.0) + bootsnap (1.13.0) msgpack (~> 1.2) builder (3.2.4) bundler-audit (0.9.1) @@ -145,9 +145,9 @@ GEM diff-lcs (1.5.0) digest (3.1.0) docile (1.4.0) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) + dotenv (2.8.1) + dotenv-rails (2.8.1) + dotenv (= 2.8.1) railties (>= 3.2) encryptor (3.0.0) erb_lint (0.1.3) @@ -158,14 +158,14 @@ GEM rainbow rubocop smart_properties - erubi (1.10.0) + erubi (1.11.0) excon (0.92.4) factory_bot (6.2.1) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (2.21.0) + faker (2.22.0) i18n (>= 1.8.11, < 2) ffi (1.15.5) globalid (1.0.0) @@ -211,7 +211,7 @@ GEM method_source (1.0.0) mini_mime (1.1.2) minitest (5.16.2) - msgpack (1.5.3) + msgpack (1.5.4) net-imap (0.2.3) digest net-protocol @@ -251,7 +251,7 @@ GEM parallel (1.22.1) parser (3.1.2.0) ast (~> 2.4.1) - pg (1.4.1) + pg (1.4.2) possessive (1.0.1) postcodes_io (0.4.0) excon (~> 0.39) @@ -375,14 +375,11 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sentry-rails (5.3.1) + sentry-rails (5.4.1) railties (>= 5.0) - sentry-ruby-core (~> 5.3.1) - sentry-ruby (5.3.1) + sentry-ruby (~> 5.4.1) + sentry-ruby (5.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) - sentry-ruby-core (= 5.3.1) - sentry-ruby-core (5.3.1) - concurrent-ruby simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -392,7 +389,7 @@ GEM smart_properties (1.17.0) stimulus-rails (1.1.0) railties (>= 6.0.0) - strscan (3.0.3) + strscan (3.0.4) thor (1.2.1) timecop (0.9.5) timeout (0.3.0) @@ -410,7 +407,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.14.0) + webmock (3.16.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/yarn.lock b/yarn.lock index 04538a43c..a6cacf010 100644 --- a/yarn.lock +++ b/yarn.lock @@ -85,38 +85,38 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.18.8": +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": version "7.18.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== "@babel/core@^7.17.7": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59" - integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g== + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" + integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== dependencies: "@ampproject/remapping" "^2.1.0" "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" + "@babel/generator" "^7.18.10" "@babel/helper-compilation-targets" "^7.18.9" "@babel/helper-module-transforms" "^7.18.9" "@babel/helpers" "^7.18.9" - "@babel/parser" "^7.18.9" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/parser" "^7.18.10" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.10" + "@babel/types" "^7.18.10" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5" - integrity sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug== +"@babel/generator@^7.18.10": + version "7.18.12" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" + integrity sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.18.10" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -135,7 +135,7 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.18.9": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== @@ -166,21 +166,19 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-define-polyfill-provider@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" - integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== +"@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" debug "^4.1.1" lodash.debounce "^4.0.8" resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.6", "@babel/helper-environment-visitor@^7.18.9": +"@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== @@ -214,7 +212,7 @@ dependencies: "@babel/types" "^7.18.9" -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6": +"@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== @@ -242,12 +240,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== -"@babel/helper-remap-async-to-generator@^7.18.6": +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== @@ -289,6 +287,11 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" @@ -300,14 +303,14 @@ integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== "@babel/helper-wrap-function@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.9.tgz#ae1feddc6ebbaa2fd79346b77821c3bd73a39646" - integrity sha512-cG2ru3TRAL6a60tfQflpEfs4ldiPwF6YW3zfJiRgmoFVIaC1vGnBBgatfec+ZUziPHkHSaXAuEck3Cdkf3eRpQ== + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" + integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== dependencies: "@babel/helper-function-name" "^7.18.9" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.11" + "@babel/types" "^7.18.10" "@babel/helpers@^7.18.9": version "7.18.9" @@ -327,10 +330,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.18.6", "@babel/parser@^7.18.9", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539" - integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== +"@babel/parser@^7.18.10", "@babel/parser@^7.18.11", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" + integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -348,14 +351,14 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" -"@babel/plugin-proposal-async-generator-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz#aedac81e6fc12bb643374656dd5f2605bf743d17" - integrity sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w== +"@babel/plugin-proposal-async-generator-functions@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" + integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== dependencies: - "@babel/helper-environment-visitor" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-proposal-class-properties@^7.18.6": @@ -784,15 +787,15 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-transform-runtime@^7.17.0": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f" - integrity sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg== + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" + integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ== dependencies: "@babel/helper-module-imports" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.9" - babel-plugin-polyfill-corejs2 "^0.3.1" - babel-plugin-polyfill-corejs3 "^0.5.2" - babel-plugin-polyfill-regenerator "^0.3.1" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" semver "^6.3.0" "@babel/plugin-transform-shorthand-properties@^7.18.6": @@ -831,12 +834,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-unicode-escapes@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz#0d01fb7fb2243ae1c033f65f6e3b4be78db75f27" - integrity sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw== +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -847,9 +850,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@^7.16.11": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.9.tgz#9b3425140d724fbe590322017466580844c7eaff" - integrity sha512-75pt/q95cMIHWssYtyfjVlvI+QEZQThQbKvR9xH+F/Agtw/s4Wfc2V9Bwd/P39VtixB7oWxGdH4GteTTwYJWMg== + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" + integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== dependencies: "@babel/compat-data" "^7.18.8" "@babel/helper-compilation-targets" "^7.18.9" @@ -857,7 +860,7 @@ "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.18.6" + "@babel/plugin-proposal-async-generator-functions" "^7.18.10" "@babel/plugin-proposal-class-properties" "^7.18.6" "@babel/plugin-proposal-class-static-block" "^7.18.6" "@babel/plugin-proposal-dynamic-import" "^7.18.6" @@ -917,13 +920,13 @@ "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.6" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.18.9" - babel-plugin-polyfill-corejs2 "^0.3.1" - babel-plugin-polyfill-corejs3 "^0.5.2" - babel-plugin-polyfill-regenerator "^0.3.1" + "@babel/types" "^7.18.10" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" core-js-compat "^3.22.1" semver "^6.3.0" @@ -945,36 +948,37 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" - integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== +"@babel/template@^7.18.10", "@babel/template@^7.18.6": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" -"@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98" - integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg== +"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.9": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" + integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" + "@babel/generator" "^7.18.10" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.18.9" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/parser" "^7.18.11" + "@babel/types" "^7.18.10" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.9.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" - integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== +"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.9.6": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" + integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== dependencies: + "@babel/helper-string-parser" "^7.18.10" "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" @@ -1008,15 +1012,20 @@ resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.1.0.tgz#20215251e5afe6e0a3787285181ba1bfc9097df0" integrity sha512-iDMHUhiEJ1xFeicyHcZQQgBzhtk5mPR0QZO3L6wtqzMsJEk2TKECuCQTGKjm+KJTHVY0dKq1dOOAWvODjpd2Mg== -"@humanwhocodes/config-array@^0.9.2": - version "0.9.5" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" - integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== +"@humanwhocodes/config-array@^0.10.4": + version "0.10.4" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" + integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" +"@humanwhocodes/gitignore-to-minimatch@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" + integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== + "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" @@ -1097,9 +1106,9 @@ fastq "^1.6.0" "@rollup/plugin-commonjs@^22.0.0": - version "22.0.1" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.1.tgz#f7cb777d20de3eeeaf994f39080115c336bef810" - integrity sha512-dGfEZvdjDHObBiP5IvwTKMVeq/tBZGMBHZFMdIV1ClMM/YoWS34xrHFGfag9SN2ZtMgNZRFruqvxZQEa70O6nQ== + version "22.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz#ee8ca8415cda30d383b4096aad5222435b4b69b6" + integrity sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg== dependencies: "@rollup/pluginutils" "^3.1.0" commondir "^1.0.1" @@ -1228,9 +1237,9 @@ integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*", "@types/node@>=10.0.0": - version "18.0.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.6.tgz#0ba49ac517ad69abe7a1508bc9b3a5483df9d5d7" - integrity sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw== + version "18.6.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.4.tgz#fd26723a8a3f8f46729812a7f9b4fc2d1608ed39" + integrity sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1447,7 +1456,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: +acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -1703,29 +1712,29 @@ babel-plugin-macros@^3.1.0: cosmiconfig "^7.0.0" resolve "^1.19.0" -babel-plugin-polyfill-corejs2@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" - integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== +babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== dependencies: - "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" - integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== +babel-plugin-polyfill-corejs3@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/helper-define-polyfill-provider" "^0.3.2" core-js-compat "^3.21.0" -babel-plugin-polyfill-regenerator@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" - integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== +babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz#8f51809b6d5883e07e71548d75966ff7635527fe" + integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" + "@babel/helper-define-polyfill-provider" "^0.3.2" babel-walk@3.0.0-canary-5: version "3.0.0-canary-5" @@ -1845,15 +1854,15 @@ browser-sync@^2.27.9: ua-parser-js "1.0.2" yargs "^17.3.1" -browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.2: - version "4.21.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.2.tgz#59a400757465535954946a400b841ed37e2b4ecf" - integrity sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA== +browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3: + version "4.21.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" + integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== dependencies: - caniuse-lite "^1.0.30001366" - electron-to-chromium "^1.4.188" + caniuse-lite "^1.0.30001370" + electron-to-chromium "^1.4.202" node-releases "^2.0.6" - update-browserslist-db "^1.0.4" + update-browserslist-db "^1.0.5" bs-recipes@1.3.4: version "1.3.4" @@ -1870,7 +1879,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -builtin-modules@^3.0.0: +builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== @@ -1914,10 +1923,10 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001366: - version "1.0.30001368" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001368.tgz#c5c06381c6051cd863c45021475434e81936f713" - integrity sha512-wgfRYa9DenEomLG/SdWgQxpIyvdtH3NW8Vq+tB6AwR9e56iOIcu1im5F/wNdDf04XlKHXqIx4N8Jo0PemeBenQ== +caniuse-lite@^1.0.30001370: + version "1.0.30001374" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz#3dab138e3f5485ba2e74bd13eca7fe1037ce6f57" + integrity sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw== chalk@^1.1.3: version "1.1.3" @@ -1955,9 +1964,9 @@ character-parser@^2.2.0: is-regex "^1.0.3" chart.js@^3.6.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" - integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== "chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1, chokidar@^3.5.3: version "3.5.3" @@ -2133,17 +2142,17 @@ copy-webpack-plugin@^10.2.4: serialize-javascript "^6.0.0" core-js-compat@^3.21.0, core-js-compat@^3.22.1: - version "3.23.5" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.5.tgz#11edce2f1c4f69a96d30ce77c805ce118909cd5b" - integrity sha512-fHYozIFIxd+91IIbXJgWd/igXIc8Mf9is0fusswjnGIWVG96y2cwyUdlCkGOw6rMLHKAxg7xtCIVaHsyOUnJIg== + version "3.24.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.24.1.tgz#d1af84a17e18dfdd401ee39da9996f9a7ba887de" + integrity sha512-XhdNAGeRnTpp8xbD+sR/HFDK9CbeeeqXT6TuofXh3urqEevzkWmLRgrVoykodsw8okqo2pu1BOmuCKrHx63zdw== dependencies: - browserslist "^4.21.2" + browserslist "^4.21.3" semver "7.0.0" core-js@^3.21.1, core-js@^3.4.0: - version "3.23.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.5.tgz#1f82b0de5eece800827a2f59d597509c67650475" - integrity sha512-7Vh11tujtAZy82da4duVreQysIoO2EvVrur7y6IzZkH1IHPSekuDi8Vuw1+YKjkbfWLRD7Nc9ICQ/sIUDutcyg== + version "3.24.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.24.1.tgz#cf7724d41724154010a6576b7b57d94c5d66e64f" + integrity sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg== cors@~2.8.5: version "2.8.5" @@ -2383,10 +2392,10 @@ ejs@^3.1.6: dependencies: jake "^10.8.5" -electron-to-chromium@^1.4.188: - version "1.4.198" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.198.tgz#36a8e7871046f7f94c01dc0133912fd5cf226c6a" - integrity sha512-jwqQPdKGeAslcq8L+1SZZgL6uDiIDmTe9Gq4brsdWAH27y7MJ2g9Ue6MyST3ogmSM49EAQP7bype1V5hsuNrmQ== +electron-to-chromium@^1.4.202: + version "1.4.211" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.211.tgz#afaa8b58313807501312d598d99b953568d60f91" + integrity sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A== element-closest@^2.0.2: version "2.0.2" @@ -2440,7 +2449,7 @@ engine.io@~6.2.0: engine.io-parser "~5.0.3" ws "~8.2.3" -enhanced-resolve@^5.9.3: +enhanced-resolve@^5.10.0: version "5.10.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== @@ -2695,12 +2704,13 @@ eslint-visitor-keys@^3.3.0: integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@^8.13.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.20.0.tgz#048ac56aa18529967da8354a478be4ec0a2bc81b" - integrity sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA== + version "8.21.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.21.0.tgz#1940a68d7e0573cef6f50037addee295ff9be9ef" + integrity sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA== dependencies: "@eslint/eslintrc" "^1.3.0" - "@humanwhocodes/config-array" "^0.9.2" + "@humanwhocodes/config-array" "^0.10.4" + "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -2710,14 +2720,17 @@ eslint@^8.13.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.2" + espree "^9.3.3" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" + find-up "^5.0.0" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.15.0" + globby "^11.1.0" + grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2735,12 +2748,12 @@ eslint@^8.13.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.3.2: - version "9.3.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" - integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== +espree@^9.3.2, espree@^9.3.3: + version "9.3.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" + integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== dependencies: - acorn "^8.7.1" + acorn "^8.8.0" acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" @@ -2854,9 +2867,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastest-levenshtein@^1.0.12: - version "1.0.14" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.14.tgz#9054384e4b7a78c88d01a4432dc18871af0ac859" - integrity sha512-tFfWHjnuUfKE186Tfgr+jtaFc0mZTApEgKDOeyN+FwOqRkO/zK/3h1AiRd8u8CY53owL3CUmGr/oI9p/RdyLTA== + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== fastq@^1.6.0: version "1.13.0" @@ -2938,6 +2951,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3176,6 +3197,11 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + gray-matter@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" @@ -3461,11 +3487,11 @@ is-buffer@^1.1.5: integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-builtin-module@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.1.0.tgz#6fdb24313b1c03b75f8b9711c0feb8c30b903b00" - integrity sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0" + integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== dependencies: - builtin-modules "^3.0.0" + builtin-modules "^3.3.0" is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" @@ -3473,9 +3499,9 @@ is-callable@^1.1.4, is-callable@^1.2.4: integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== dependencies: has "^1.0.3" @@ -3889,6 +3915,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -4382,6 +4415,13 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -4403,6 +4443,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -5038,9 +5085,9 @@ rimraf@^3.0.2: glob "^7.1.3" rollup@^2.62.0: - version "2.77.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.0.tgz#749eaa5ac09b6baa52acc076bc46613eddfd53f4" - integrity sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g== + version "2.77.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.2.tgz#6b6075c55f9cc2040a5912e6e062151e42e2c4e3" + integrity sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g== optionalDependencies: fsevents "~2.3.2" @@ -5087,9 +5134,9 @@ sass-loader@^12.6.0: neo-async "^2.6.2" sass@^1.45.1, sass@^1.49.9: - version "1.53.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.53.0.tgz#eab73a7baac045cc57ddc1d1ff501ad2659952eb" - integrity sha512-zb/oMirbKhUgRQ0/GFz8TSAwRq2IlR29vOUJZOx0l8sV+CkHUfHa4u5nqrG+1VceZp7Jfj59SVW9ogdhTvJDcQ== + version "1.54.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.3.tgz#37baa2652f7f1fdadb73240ee9a2b9b81fabb5c4" + integrity sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -5811,9 +5858,9 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.16.2" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def" - integrity sha512-AaQNokTNgExWrkEYA24BTNMSjyqEXPSfhqoS0AxmHkCJ4U+Dyy5AvbGV/sqxuxficEfGGoX3zWw9R7QpLFfEsg== + version "3.16.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.3.tgz#94c7a63337ee31227a18d03b8a3041c210fd1f1d" + integrity sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw== unbox-primitive@^1.0.2: version "1.0.2" @@ -5863,7 +5910,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.4: +update-browserslist-db@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== @@ -5911,7 +5958,7 @@ void-elements@^3.1.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -watchpack@^2.3.1: +watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== @@ -5958,20 +6005,20 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.70.0: - version "5.73.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" - integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== + version "5.74.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" + integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" + acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.3" + enhanced-resolve "^5.10.0" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" @@ -5984,7 +6031,7 @@ webpack@^5.70.0: schema-utils "^3.1.0" tapable "^2.1.1" terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" + watchpack "^2.4.0" webpack-sources "^3.2.3" which-boxed-primitive@^1.0.2: @@ -6100,9 +6147,9 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3: integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-parser@^21.0.0: - version "21.0.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" - integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@17.1.1: version "17.1.1" @@ -6129,3 +6176,8 @@ yargs@^17.3.1: string-width "^4.2.3" y18n "^5.0.5" yargs-parser "^21.0.0" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 981a81631ba33ac608c44421c9f82460eedfcd0b Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 5 Aug 2022 16:24:19 +0100 Subject: [PATCH 13/29] allow searching by a Property Postcode, when case log is supported housing (#818) --- app/models/case_log.rb | 10 ++++++---- spec/models/case_log_spec.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 9b703a510..be31c9200 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -57,11 +57,13 @@ class CaseLog < ApplicationRecord scope :filter_by_tenant_code, ->(tenant_code) { where("tenancycode ILIKE ?", "%#{tenant_code}%") } scope :filter_by_propcode, ->(propcode) { where("propcode ILIKE ?", "%#{propcode}%") } scope :filter_by_postcode, ->(postcode_full) { where("REPLACE(postcode_full, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") } + scope :filter_by_location_postcode, ->(postcode_full) { left_joins(:location).where("REPLACE(locations.postcode, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") } scope :search_by, lambda { |param| - filter_by_id(param) - .or(filter_by_tenant_code(param)) - .or(filter_by_propcode(param)) - .or(filter_by_postcode(param)) + filter_by_location_postcode(param) + .or(filter_by_tenant_code(param)) + .or(filter_by_propcode(param)) + .or(filter_by_postcode(param)) + .or(filter_by_id(param)) } AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index 9520feffe..704879df5 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -2073,6 +2073,20 @@ RSpec.describe CaseLog do expect(result.count).to eq(1) expect(result.first.id).to eq case_log_to_search.id end + + context "when case log is supported housing" do + let(:location) { FactoryBot.create(:location, postcode: "W6 0ST") } + + before do + case_log_to_search.update!(needstype: 2, location:) + end + + it "allows searching by a Property Postcode" do + result = described_class.filter_by_location_postcode("W6 0ST") + expect(result.count).to eq(1) + expect(result.first.id).to eq case_log_to_search.id + end + end end describe "#search_by" do @@ -2100,6 +2114,20 @@ RSpec.describe CaseLog do expect(result.first.id).to eq case_log_to_search.id end + context "when case log is supported housing" do + let(:location) { FactoryBot.create(:location, postcode: "W6 0ST") } + + before do + case_log_to_search.update!(needstype: 2, location:) + end + + it "allows searching by a Property Postcode" do + result = described_class.search_by("W6 0ST") + expect(result.count).to eq(1) + expect(result.first.id).to eq case_log_to_search.id + end + end + context "when postcode has spaces and lower case letters" do let(:matching_postcode_lower_case_with_spaces) { case_log_to_search.postcode_full.downcase.chars.insert(3, " ").join } From 51acdf4c5b0305646ac4f6e63da1887d5dcd4039 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Mon, 8 Aug 2022 10:49:42 +0100 Subject: [PATCH 14/29] CLDC-1171: User validation order (#817) * User validation order * Rubocop * Remove redundant method * Clearer error message * Role can be optional * Hint text content --- app/controllers/users_controller.rb | 8 +--- app/models/user.rb | 32 ++++++++------ app/views/users/new.html.erb | 5 ++- config/initializers/devise.rb | 2 +- config/locales/en.yml | 4 +- spec/models/user_spec.rb | 44 +++++++++++++++++++ spec/requests/users_controller_spec.rb | 1 - .../imports/user_import_service_spec.rb | 2 +- 8 files changed, 71 insertions(+), 27 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c22570a2e..cd82742b6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,7 +58,7 @@ class UsersController < ApplicationController end elsif user_params.key?("password") format_error_messages - @minimum_password_length = User.password_length.min + @minimum_password_length = Devise.password_length.min render "devise/passwords/edit", locals: { resource: @user, resource_name: "user" }, status: :unprocessable_entity else format_error_messages @@ -79,7 +79,6 @@ class UsersController < ApplicationController redirect_to created_user_redirect_path else unless @resource.errors[:organisation].empty? - @resource.errors.add(:organisation_id, message: @resource.errors[:organisation]) @resource.errors.delete(:organisation) end render :new, status: :unprocessable_entity @@ -87,7 +86,7 @@ class UsersController < ApplicationController end def edit_password - @minimum_password_length = User.password_length.min + @minimum_password_length = Devise.password_length.min render "devise/passwords/edit", locals: { resource: @user, resource_name: "user" } end @@ -114,9 +113,6 @@ private if user_params[:role].present? && !current_user.assignable_roles.key?(user_params[:role].to_sym) @resource.errors.add :role, I18n.t("validations.role.invalid") end - if user_params[:role].empty? - @resource.errors.add :role, I18n.t("activerecord.errors.models.user.attributes.role.blank") - end end def format_error_messages diff --git a/app/models/user.rb b/app/models/user.rb index f9ed4a0e9..f3233bff6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,16 +1,23 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :omniauthable - include Helpers::Email - devise :database_authenticatable, :recoverable, :rememberable, :validatable, + devise :database_authenticatable, :recoverable, :rememberable, :trackable, :lockable, :two_factor_authenticatable, :confirmable, :timeoutable - belongs_to :organisation + # Marked as optional because we validate organisation_id below instead so that + # the error message is linked to the right field on the form + belongs_to :organisation, optional: true has_many :owned_case_logs, through: :organisation, dependent: :delete_all has_many :managed_case_logs, through: :organisation - validate :validate_email - validates :name, :email, presence: true + validates :name, presence: true + validates :email, presence: true + validates :email, uniqueness: { allow_blank: true, case_sensitive: true, if: :will_save_change_to_email? } + validates :email, format: { with: Devise.email_regexp, allow_blank: true, if: :will_save_change_to_email? } + validates :password, presence: { if: :password_required? } + validates :password, confirmation: { if: :password_required? } + validates :password, length: { within: Devise.password_length, allow_blank: true } + validates :organisation_id, presence: true has_paper_trail ignore: %w[last_sign_in_at current_sign_in_at @@ -156,15 +163,12 @@ class User < ApplicationRecord super && active? end -private +protected - def validate_email - unless email_valid?(email) - if User.exists?(["email LIKE ?", "%#{email}%"]) - errors.add :email, I18n.t("activerecord.errors.models.user.attributes.email.taken") - else - errors.add :email, I18n.t("activerecord.errors.models.user.attributes.email.invalid") - end - end + # Checks whether a password is needed or not. For validations only. + # Passwords are always required if it's a new record, or if the password + # or confirmation are being set somewhere. + def password_required? + !persisted? || !password.nil? || !password_confirmation.nil? end end diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index cf15c06e0..cf72e49f0 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -41,7 +41,7 @@ options: { disabled: [""], selected: @organisation_id ? answer_options.first : "" } %> <% end %> - <% hints_for_roles = { data_provider: ["Default role for this organisation", "Can view and submit logs for this organisation"], data_coordinator: ["Can view and submit logs for this organisation and any of its managing agents", "Can manage details for this organisation", "Can manage users for this organisation"], support: nil } %> + <% hints_for_roles = { data_provider: ["Can view and submit logs for this organisation"], data_coordinator: ["Can view and submit logs for this organisation and any of its managing agents", "Can manage details for this organisation", "Can manage users for this organisation"], support: nil } %> <% roles_with_hints = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize, description: hints_for_roles[key.to_sym]) } %> @@ -52,7 +52,8 @@ lambda { |option| option.description&.map { |hint| content_tag(:li, hint) }&.reduce(:+) }, - legend: { text: "Role", size: "m" } %> + legend: { text: "Role (optional)", size: "m" }, + hint: { text: "You do not need to select a role if the user is a data protection officer only. You can tell us that this user is a data protection officer after you have invited them." } %> <%= f.govuk_submit "Continue" %>
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 83c73db72..cd1a30ee8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -184,7 +184,7 @@ Devise.setup do |config| # Email regex used to validate email formats. It simply asserts that # one (and only one) @ exists in the given string. This is mainly # to give user feedback and not to assert the e-mail validity. - config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + config.email_regexp = URI::MailTo::EMAIL_REGEXP # ==> Configuration for :timeoutable # The time you want to timeout the user session without activity. After this diff --git a/config/locales/en.yml b/config/locales/en.yml index 4fc1c7b61..56b70d397 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -78,8 +78,8 @@ en: user: attributes: organisation_id: - blank: "Enter the existing organisation’s name" - invalid: "Enter the existing organisation’s name" + blank: "Select the user’s organisation" + invalid: "Select the user’s organisation" name: blank: "Enter a name" email: diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index caf8d03d3..5b68a736e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -225,4 +225,48 @@ RSpec.describe User, type: :model do end end end + + describe "validate" do + context "when a user does not have values for required fields" do + let(:user) { described_class.new } + + before do + user.validate + end + + it "validates name, email and organisation presence in the correct order" do + expect(user.errors.map(&:attribute).uniq).to eq(%i[name email password organisation_id]) + end + end + + context "when a too short password is entered" do + let(:password) { "123" } + let(:error_message) { "Validation failed: Password #{I18n.t('errors.messages.too_short', count: 8)}" } + + it "validates password length" do + expect { FactoryBot.create(:user, password:) } + .to raise_error(ActiveRecord::RecordInvalid, error_message) + end + end + + context "when an invalid email is entered" do + let(:invalid_email) { "not_an_email" } + let(:error_message) { "Validation failed: Email #{I18n.t('activerecord.errors.models.user.attributes.email.invalid')}" } + + it "validates email format" do + expect { FactoryBot.create(:user, email: invalid_email) } + .to raise_error(ActiveRecord::RecordInvalid, error_message) + end + end + + context "when the email entered has already been used" do + let(:user) { FactoryBot.create(:user) } + let(:error_message) { "Validation failed: Email #{I18n.t('activerecord.errors.models.user.attributes.email.taken')}" } + + it "validates email uniqueness" do + expect { FactoryBot.create(:user, email: user.email) } + .to raise_error(ActiveRecord::RecordInvalid, error_message) + end + end + end end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index c5303776a..1d3b55cb0 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -930,7 +930,6 @@ RSpec.describe UsersController, type: :request do expect(response).to have_http_status(:unprocessable_entity) expect(page).to have_content(I18n.t("activerecord.errors.models.user.attributes.name.blank")) expect(page).to have_content(I18n.t("activerecord.errors.models.user.attributes.email.blank")) - expect(page).to have_content(I18n.t("activerecord.errors.models.user.attributes.role.blank")) end end end diff --git a/spec/services/imports/user_import_service_spec.rb b/spec/services/imports/user_import_service_spec.rb index 02a776ecc..66ea6eb07 100644 --- a/spec/services/imports/user_import_service_spec.rb +++ b/spec/services/imports/user_import_service_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Imports::UserImportService do end it "refuses to create a user belonging to a non existing organisation" do - expect(logger).to receive(:error).with(/Organisation must exist/) + expect(logger).to receive(:error).with(/ActiveRecord::RecordInvalid/) import_service.create_users("user_directory") end From 2c44fd1def5e6db4821766030fc1824d90d087d3 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Mon, 8 Aug 2022 10:53:13 +0100 Subject: [PATCH 15/29] Add accessible autocomplete to new user org select (#819) --- app/views/users/new.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index cf72e49f0..b3311bcfb 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -37,6 +37,7 @@ answer_options, :id, :name, + "data-controller": "accessible-autocomplete", label: { text: "Organisation", size: "m" }, options: { disabled: [""], selected: @organisation_id ? answer_options.first : "" } %> <% end %> From d56a4e78fde6e0ae9a856697e213e2b5e76b354c Mon Sep 17 00:00:00 2001 From: Ted-U <92022120+Ted-U@users.noreply.github.com> Date: Mon, 8 Aug 2022 17:44:47 +0100 Subject: [PATCH 16/29] CLDC-1361-only stock owning orgs see schemes in nav bar (#822) * only stock owning orgs see schemes in nav bar * lint * added to organisation factory and fixed tests --- app/helpers/navigation_items_helper.rb | 2 +- spec/factories/organisation.rb | 1 + spec/features/organisation_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/helpers/navigation_items_helper.rb b/app/helpers/navigation_items_helper.rb index dbaf64296..4bf5a5fec 100644 --- a/app/helpers/navigation_items_helper.rb +++ b/app/helpers/navigation_items_helper.rb @@ -9,7 +9,7 @@ module NavigationItemsHelper NavigationItem.new("Logs", case_logs_path, logs_current?(path)), NavigationItem.new("Schemes", "/schemes", supported_housing_schemes_current?(path)), ] - elsif current_user.data_coordinator? + elsif current_user.data_coordinator? && current_user.organisation.holds_own_stock? [ NavigationItem.new("Logs", case_logs_path, logs_current?(path)), NavigationItem.new("Schemes", "/schemes", subnav_supported_housing_schemes_path?(path)), diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb index e88abf0c5..5ce6cfc03 100644 --- a/spec/factories/organisation.rb +++ b/spec/factories/organisation.rb @@ -8,6 +8,7 @@ FactoryBot.define do postcode { "SW1P 4DF" } created_at { Time.zone.now } updated_at { Time.zone.now } + holds_own_stock { true } end factory :organisation_la do diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb index ee7ec0c37..07a704913 100644 --- a/spec/features/organisation_spec.rb +++ b/spec/features/organisation_spec.rb @@ -39,6 +39,28 @@ RSpec.describe "User Features" do click_link("About your organisation") expect(page).to have_current_path("/organisations/#{org_id}/details") end + + context "when the user is a coordinator and the organisation does not hold housing stock" do + before do + organisation.update(holds_own_stock: false) + end + + it "does not show schemes in the navigation bar" do + visit("/logs") + expect(page).not_to have_link("Schemes", href: "/schemes") + end + end + + context "when the user is a coordinator and the organisation holds housing stock" do + before do + organisation.update(holds_own_stock: true) + end + + it "shows schemes in the navigation bar" do + visit("/logs") + expect(page).to have_link("Schemes", href: "/schemes") + end + end end context "when users are part of organisation" do From 5c12a4f604394b08a13e03912abfbd16948de49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Meny?= Date: Tue, 9 Aug 2022 10:49:39 +0100 Subject: [PATCH 17/29] CLDC-1219: Single import task (#821) --- app/services/imports/user_import_service.rb | 2 +- app/services/storage_service.rb | 5 ++ lib/tasks/data_import.rake | 2 +- lib/tasks/full_import.rake | 30 ++++++++ spec/lib/tasks/full_import_spec.rb | 85 +++++++++++++++++++++ spec/services/storage_service_spec.rb | 67 ++++++++++++---- 6 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 lib/tasks/full_import.rake create mode 100644 spec/lib/tasks/full_import_spec.rb diff --git a/app/services/imports/user_import_service.rb b/app/services/imports/user_import_service.rb index aec227f54..1d1fbc84e 100644 --- a/app/services/imports/user_import_service.rb +++ b/app/services/imports/user_import_service.rb @@ -13,8 +13,8 @@ module Imports def create_user(xml_document) organisation = Organisation.find_by(old_org_id: user_field_value(xml_document, "institution")) old_user_id = user_field_value(xml_document, "id") - name = user_field_value(xml_document, "full-name") email = user_field_value(xml_document, "email").downcase.strip + name = user_field_value(xml_document, "full-name") || email deleted = user_field_value(xml_document, "deleted") if User.find_by(old_user_id:, organisation:) diff --git a/app/services/storage_service.rb b/app/services/storage_service.rb index 1e9e374ce..766af69fa 100644 --- a/app/services/storage_service.rb +++ b/app/services/storage_service.rb @@ -13,6 +13,11 @@ class StorageService .flat_map { |response| response.contents.map(&:key) } end + def folder_present?(folder) + response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) + response.key_count == 1 + end + def get_file_io(file_name) @client.get_object(bucket: @configuration.bucket_name, key: file_name) .body diff --git a/lib/tasks/data_import.rake b/lib/tasks/data_import.rake index 54ebe60d4..94553f0da 100644 --- a/lib/tasks/data_import.rake +++ b/lib/tasks/data_import.rake @@ -1,5 +1,5 @@ namespace :core do - desc "Import data XMLs from Softwire system" + desc "Import data XMLs from legacy CORE" task :data_import, %i[type path] => :environment do |_task, args| type = args[:type] path = args[:path] diff --git a/lib/tasks/full_import.rake b/lib/tasks/full_import.rake new file mode 100644 index 000000000..42ac083f1 --- /dev/null +++ b/lib/tasks/full_import.rake @@ -0,0 +1,30 @@ +Import = Struct.new("Import", :import_class, :import_method, :folder) + +namespace :core do + desc "Import all data XMLs from legacy CORE" + task :full_import, %i[path] => :environment do |_task, args| + path = args[:path] + raise "Usage: rake core:full_import['path/to/main_folder']" if path.blank? + + storage_service = StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + + import_list = [ + Import.new(Imports::OrganisationImportService, :create_organisations, "institution"), + Import.new(Imports::SchemeImportService, :create_schemes, "mgmtgroups"), + Import.new(Imports::SchemeLocationImportService, :create_scheme_locations, "schemes"), + Import.new(Imports::UserImportService, :create_users, "user"), + Import.new(Imports::DataProtectionConfirmationImportService, :create_data_protection_confirmations, "dataprotect"), + Import.new(Imports::OrganisationRentPeriodImportService, :create_organisation_rent_periods, "rent-period"), + Import.new(Imports::CaseLogsImportService, :create_logs, "logs"), + ] + + import_list.each do |import| + folder_path = File.join(path, import.folder, "") + if storage_service.folder_present?(folder_path) + import.import_class.new(storage_service).send(import.import_method, folder_path) + else + Rails.logger.info("#{folder_path} does not exist, skipping #{import.import_class}") + end + end + end +end diff --git a/spec/lib/tasks/full_import_spec.rb b/spec/lib/tasks/full_import_spec.rb new file mode 100644 index 000000000..360fb8516 --- /dev/null +++ b/spec/lib/tasks/full_import_spec.rb @@ -0,0 +1,85 @@ +require "rails_helper" +require "rake" + +describe "rake core:full_import", type: :task do + subject(:task) { Rake::Task["core:full_import"] } + + let(:instance_name) { "paas_import_instance" } + let(:storage_service) { instance_double(StorageService) } + let(:paas_config_service) { instance_double(PaasConfigurationService) } + + before do + Rake.application.rake_require("tasks/full_import") + Rake::Task.define_task(:environment) + task.reenable + + allow(StorageService).to receive(:new).and_return(storage_service) + allow(PaasConfigurationService).to receive(:new).and_return(paas_config_service) + allow(ENV).to receive(:[]) + allow(ENV).to receive(:[]).with("IMPORT_PAAS_INSTANCE").and_return(instance_name) + end + + context "when starting a full import" do + let(:fixture_path) { "spec/fixtures/imports" } + let(:case_logs_service) { instance_double(Imports::CaseLogsImportService) } + let(:rent_period_service) { instance_double(Imports::OrganisationRentPeriodImportService) } + let(:data_protection_service) { instance_double(Imports::DataProtectionConfirmationImportService) } + let(:user_service) { instance_double(Imports::UserImportService) } + let(:location_service) { instance_double(Imports::SchemeLocationImportService) } + let(:scheme_service) { instance_double(Imports::SchemeImportService) } + let(:organisation_service) { instance_double(Imports::OrganisationImportService) } + + before do + allow(Imports::OrganisationImportService).to receive(:new).and_return(organisation_service) + allow(Imports::SchemeImportService).to receive(:new).and_return(scheme_service) + allow(Imports::SchemeLocationImportService).to receive(:new).and_return(location_service) + allow(Imports::UserImportService).to receive(:new).and_return(user_service) + allow(Imports::DataProtectionConfirmationImportService).to receive(:new).and_return(data_protection_service) + allow(Imports::OrganisationRentPeriodImportService).to receive(:new).and_return(rent_period_service) + allow(Imports::CaseLogsImportService).to receive(:new).and_return(case_logs_service) + end + + it "raises an exception if no parameters are provided" do + expect { task.invoke }.to raise_error(/Usage/) + end + + context "with all folders being present" do + before { allow(storage_service).to receive(:folder_present?).and_return(true) } + + it "calls every import method with the correct folder" do + expect(organisation_service).to receive(:create_organisations).with("#{fixture_path}/institution/") + expect(scheme_service).to receive(:create_schemes).with("#{fixture_path}/mgmtgroups/") + expect(location_service).to receive(:create_scheme_locations).with("#{fixture_path}/schemes/") + expect(user_service).to receive(:create_users).with("#{fixture_path}/user/") + expect(data_protection_service).to receive(:create_data_protection_confirmations).with("#{fixture_path}/dataprotect/") + expect(rent_period_service).to receive(:create_organisation_rent_periods).with("#{fixture_path}/rent-period/") + expect(case_logs_service).to receive(:create_logs).with("#{fixture_path}/logs/") + + task.invoke(fixture_path) + end + end + + context "when a specific folders are missing" do + before do + allow(storage_service).to receive(:folder_present?).and_return(true) + allow(storage_service).to receive(:folder_present?).with("#{fixture_path}/mgmtgroups/").and_return(false) + allow(storage_service).to receive(:folder_present?).with("#{fixture_path}/schemes/").and_return(false) + end + + it "only calls import methods for existing folders" do + expect(organisation_service).to receive(:create_organisations) + expect(user_service).to receive(:create_users) + expect(data_protection_service).to receive(:create_data_protection_confirmations) + expect(rent_period_service).to receive(:create_organisation_rent_periods) + expect(case_logs_service).to receive(:create_logs) + + expect(scheme_service).not_to receive(:create_schemes) + expect(location_service).not_to receive(:create_scheme_locations) + expect(Rails.logger).to receive(:info).with("spec/fixtures/imports/mgmtgroups/ does not exist, skipping Imports::SchemeImportService") + expect(Rails.logger).to receive(:info).with("spec/fixtures/imports/schemes/ does not exist, skipping Imports::SchemeLocationImportService") + + task.invoke(fixture_path) + end + end + end +end diff --git a/spec/services/storage_service_spec.rb b/spec/services/storage_service_spec.rb index 4e79228f6..d19d51ed8 100644 --- a/spec/services/storage_service_spec.rb +++ b/spec/services/storage_service_spec.rb @@ -99,7 +99,7 @@ RSpec.describe StorageService do end end - context "when we create a storage service and list files" do + context "when we create a storage service" do subject(:storage_service) { described_class.new(PaasConfigurationService.new, instance_name) } let(:s3_client_stub) { Aws::S3::Client.new(stub_responses: true) } @@ -110,22 +110,59 @@ RSpec.describe StorageService do allow(Aws::S3::Client).to receive(:new).and_return(s3_client_stub) end - it "returns a list with all present file names in a given folder" do - expected_filenames = %w[my_folder/my_file1.xml my_folder/my_file2.xml] - s3_client_stub.stub_responses(:list_objects_v2, { - contents: [ - { - key: expected_filenames[0], - }, - { - key: expected_filenames[1], - }, - ], - }) + context "and we list files based on a prefix" do + let(:expected_filenames) { %w[my_folder/my_file1.xml my_folder/my_file2.xml] } + + before do + s3_client_stub.stub_responses(:list_objects_v2, { + contents: [ + { + key: expected_filenames[0], + }, + { + key: expected_filenames[1], + }, + ], + }) + end + + it "returns a list with all present file names in a given folder" do + filenames = storage_service.list_files("my_folder") + expect(filenames).to eq(expected_filenames) + end + end - filenames = storage_service.list_files("my_folder") + context "and we check for an existing folder" do + before do + expected_filenames = %w[my_folder/my_file1.xml] + s3_client_stub.stub_responses(:list_objects_v2, { + key_count: 1, + contents: [ + { + key: expected_filenames[0], + }, + ], + }) + end + + it "returns true" do + response = storage_service.folder_present?("my_folder") + expect(response).to be_truthy + end + end - expect(filenames).to eq(expected_filenames) + context "and we check for a folder that does not exists" do + before do + s3_client_stub.stub_responses(:list_objects_v2, { + key_count: 0, + contents: [], + }) + end + + it "returns false" do + response = storage_service.folder_present?("my_folder") + expect(response).to be_falsey + end end end end From b0aa8611d4d184950279b1c0b9112700979185fb Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Tue, 9 Aug 2022 11:19:40 +0100 Subject: [PATCH 18/29] Fix checkbox validation (#823) * Don't crash when invalid checkbox answers are selected * Ensure all checkbox selections are preserved not just the first that doesn't match the all * Rubocop --- app/controllers/form_controller.rb | 5 +++-- spec/features/form/checkboxes_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 5a8d6375d..ad3f858e3 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -73,7 +73,7 @@ private end if session["fields"] session["fields"].each do |field, value| - unless @case_log.form.get_question(field, @case_log).type == "date" + unless @case_log.form.get_question(field, @case_log)&.type == "date" @case_log[field] = value end end @@ -161,10 +161,11 @@ private def question_missing_response?(responses_for_page, question) if %w[checkbox validation_override].include?(question.type) - question.answer_options.keys.reject { |x| x.match(/divider/) }.all? do |option| + answered = question.answer_options.keys.reject { |x| x.match(/divider/) }.map do |option| session["fields"][option] = @case_log[option] = params["case_log"][question.id].include?(option) ? 1 : 0 params["case_log"][question.id].exclude?(option) end + answered.all? else session["fields"][question.id] = @case_log[question.id] = responses_for_page[question.id] responses_for_page[question.id].nil? || responses_for_page[question.id].blank? diff --git a/spec/features/form/checkboxes_spec.rb b/spec/features/form/checkboxes_spec.rb index f6486906e..cbf5e51c1 100644 --- a/spec/features/form/checkboxes_spec.rb +++ b/spec/features/form/checkboxes_spec.rb @@ -38,4 +38,27 @@ RSpec.describe "Checkboxes" do expect(case_log["housingneeds_h"]).to eq(1) end end + + context "when a checkbox question is submitted with invalid answers" do + before do + allow(case_log).to receive(:update).and_return(false) + end + + it "shows an error summary" do + visit("/logs/#{id}/accessibility-requirements") + page.check("case-log-accessibility-requirements-housingneeds-a-field") + page.check("case-log-accessibility-requirements-housingneeds-c-field") + click_button("Save and continue") + expect(page).to have_title("Error") + end + + it "persists the original selections" do + visit("/logs/#{id}/accessibility-requirements") + page.check("case-log-accessibility-requirements-housingneeds-a-field") + page.check("case-log-accessibility-requirements-housingneeds-c-field") + click_button("Save and continue") + expect(page).to have_checked_field("case-log-accessibility-requirements-housingneeds-a-field") + expect(page).to have_checked_field("case-log-accessibility-requirements-housingneeds-c-field") + end + end end From 1b6e6938eaded281083b695a71f514e3afe921a8 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:36:13 +0100 Subject: [PATCH 19/29] Cldc 1235 conditional shortfall (#824) * Add outstanding amount conditionally and update check-answers * Duplicate changes on 2021_2022.json * Fix mis-commit --- config/forms/2021_2022.json | 25 +++++++++---------------- config/forms/2022_2023.json | 25 +++++++++---------------- 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index a81f08b9f..f008ab328 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -7887,17 +7887,15 @@ } ] }, - "outstanding_amount_known": { + "outstanding_amount": { "header": "", "description": "", "questions": { "tshortfall_known": { - "check_answer_label": "", - "header": "", - "hint_text": "", - "hidden_in_check_answers": true, + "check_answer_label": "Do you know the outstanding amount?", + "header": "Can you estimate the outstanding amount?", + "hint_text": "You only need to give an approximate figure.", "type": "radio", - "derived": true, "answer_options": { "0": { "value": "Yes" @@ -7905,19 +7903,14 @@ "1": { "value": "No" } + }, + "conditional_for": { + "tshortfall": [0] } - } - }, - "depends_on": [false] - }, - "outstanding_amount": { - "header": "", - "description": "", - "questions": { + }, "tshortfall": { "check_answer_label": "Estimated outstanding amount", - "header": "What do you expect the outstanding amount to be?", - "hint_text": "Give an estimated amount if you don’t know the exact figure.", + "header": "Estimated outstanding amount", "type": "numeric", "min": 0, "step": 0.01, diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index d7c903b25..4a43f6a26 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -7819,17 +7819,15 @@ } ] }, - "outstanding_amount_known": { + "outstanding_amount": { "header": "", "description": "", "questions": { "tshortfall_known": { - "check_answer_label": "", - "header": "", - "hint_text": "", - "hidden_in_check_answers": true, + "check_answer_label": "Do you know the outstanding amount?", + "header": "Can you estimate the outstanding amount?", + "hint_text": "You only need to give an approximate figure.", "type": "radio", - "derived": true, "answer_options": { "0": { "value": "Yes" @@ -7837,19 +7835,14 @@ "1": { "value": "No" } + }, + "conditional_for": { + "tshortfall": [0] } - } - }, - "depends_on": [false] - }, - "outstanding_amount": { - "header": "", - "description": "", - "questions": { + }, "tshortfall": { "check_answer_label": "Estimated outstanding amount", - "header": "What do you expect the outstanding amount to be?", - "hint_text": "Give an estimated amount if you don’t know the exact figure.", + "header": "Estimated outstanding amount", "type": "numeric", "min": 0, "step": 0.01, From d4f7f3492eae7bc6aedcd1a3b3bfe84f282e63a1 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 10 Aug 2022 10:05:23 +0100 Subject: [PATCH 20/29] Cldc 1235 conditional shortfall (#825) * Add outstanding amount conditionally and update check-answers * Duplicate changes on 2021_2022.json * Fix mis-commit * Feat: copy update --- config/forms/2021_2022.json | 2 +- config/forms/2022_2023.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index f008ab328..ffbdc0887 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -7843,7 +7843,7 @@ "description": "", "questions": { "hbrentshortfall": { - "check_answer_label": "Outstanding amount for basic rent and charges", + "check_answer_label": "Any outstanding amount for basic rent and charges", "header": "After the household has received any housing-related benefits, will they still need to pay for rent and charges?", "hint_text": "Also known as the ‘outstanding amount’.", "type": "radio", diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 4a43f6a26..7735e5ad9 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -7783,7 +7783,7 @@ "description": "", "questions": { "hbrentshortfall": { - "check_answer_label": "Outstanding amount for basic rent and charges", + "check_answer_label": "Any outstanding amount for basic rent and charges", "header": "After the household has received any housing-related benefits, will they still need to pay for rent and charges?", "hint_text": "Also known as the ‘outstanding amount’.", "type": "radio", From 87633111a25460db3150f24116e91d3f4c9509ff Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 10 Aug 2022 17:04:56 +0100 Subject: [PATCH 21/29] Update remote schema.rb with migrations, main features for this ticket still incoming (#827) --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index aa7304022..0baafd421 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -383,6 +383,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_02_125711) do add_foreign_key "case_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "case_logs", "schemes" add_foreign_key "locations", "schemes" + add_foreign_key "schemes", "organisations", column: "managing_organisation_id" add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "users", "organisations", on_delete: :cascade end From d7821e4286ec0126631057f26ec0097941e7aef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Meny?= Date: Thu, 11 Aug 2022 13:40:47 +0100 Subject: [PATCH 22/29] CLDC-1221: Support reading import from zip archive (#829) --- app/services/archive_storage_service.rb | 24 ++++++ app/services/s3_storage_service.rb | 78 +++++++++++++++++++ app/services/storage_service.rb | 76 ++---------------- lib/tasks/data_export.rake | 2 +- lib/tasks/data_import.rake | 2 +- lib/tasks/data_import_field.rake | 2 +- lib/tasks/full_import.rake | 19 ++--- ...5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml | 0 ...5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml | 0 .../00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml | 0 .../0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml | 0 .../0ead17cb-1668-442d-898c-0d52879ff592.xml | 0 .../166fc004-392e-47a8-acb8-1c018734882b.xml | 0 .../5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml | 0 .../893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml | 0 ...6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml | 0 ...d22326d33e389e9f1bfd546979d2c05f9e68d6.xml | 0 ...e7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml | 0 ...b3836b70b4dd9903263d5a764a5c45b964a89d.xml | 0 ...c887710550844e2551b3e0fb88dc9b4a8a642b.xml | 0 ...d81a262215a1634f0809effa683e38924d8bcb.xml | 0 ...829b1a5dfb68bb1e01c08445830c0add40907c.xml | 0 ...729b1a5dfb68bb1e01c08445830c0add40907c.xml | 0 ...717836154cd9a58f9e2f1d3077e3ab81e07613.xml | 0 ...7625a02b24ae16162aa63ae7cb33feeec0c373.xml | 0 spec/lib/tasks/data_export_spec.rb | 8 +- spec/lib/tasks/data_import_spec.rb | 18 ++--- spec/lib/tasks/date_import_field_spec.rb | 10 +-- spec/lib/tasks/full_import_spec.rb | 38 ++++----- spec/services/archive_storage_service_spec.rb | 65 ++++++++++++++++ .../exports/case_log_export_service_spec.rb | 2 +- .../case_logs_field_import_service_spec.rb | 4 +- .../imports/case_logs_import_service_spec.rb | 4 +- ...ection_confirmation_import_service_spec.rb | 4 +- .../organisation_import_service_spec.rb | 4 +- ...isation_rent_period_import_service_spec.rb | 4 +- .../imports/scheme_import_service_spec.rb | 4 +- .../scheme_location_import_service_spec.rb | 4 +- .../imports/user_import_service_spec.rb | 4 +- ...ice_spec.rb => s3_storage_service_spec.rb} | 2 +- 40 files changed, 243 insertions(+), 135 deletions(-) create mode 100644 app/services/archive_storage_service.rb create mode 100644 app/services/s3_storage_service.rb rename spec/fixtures/imports/{data_protection_confirmations => dataprotect}/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml (100%) rename spec/fixtures/imports/{organisations => institution}/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/0ead17cb-1668-442d-898c-0d52879ff592.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/166fc004-392e-47a8-acb8-1c018734882b.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml (100%) rename spec/fixtures/imports/{case_logs => logs}/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml (100%) rename spec/fixtures/imports/{schemes => mgmtgroups}/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml (100%) rename spec/fixtures/imports/{organisation_rent_periods => rent-period}/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml (100%) rename spec/fixtures/imports/{scheme_locations => schemes}/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml (100%) rename spec/fixtures/imports/{scheme_locations => schemes}/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml (100%) rename spec/fixtures/imports/{users => user}/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml (100%) rename spec/fixtures/imports/{users => user}/9ed81a262215a1634f0809effa683e38924d8bcb.xml (100%) rename spec/fixtures/imports/{users => user}/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml (100%) rename spec/fixtures/imports/{users => user}/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml (100%) rename spec/fixtures/imports/{users => user}/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml (100%) rename spec/fixtures/imports/{users => user}/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml (100%) create mode 100644 spec/services/archive_storage_service_spec.rb rename spec/services/{storage_service_spec.rb => s3_storage_service_spec.rb} (99%) diff --git a/app/services/archive_storage_service.rb b/app/services/archive_storage_service.rb new file mode 100644 index 000000000..92875d86a --- /dev/null +++ b/app/services/archive_storage_service.rb @@ -0,0 +1,24 @@ +class ArchiveStorageService < StorageService + MAX_SIZE = 50 * (1024**2) # 50MiB + + def initialize(archive_io) + super() + @archive = Zip::File.open_buffer(archive_io) + end + + def list_files(folder) + @archive.glob(File.join(folder, "*.*")) + .map(&:name) + end + + def folder_present?(folder) + !list_files(folder).empty? + end + + def get_file_io(file_name) + entry = @archive.get_entry(file_name) + raise "File too large to be extracted" if entry.size > MAX_SIZE + + entry.get_input_stream + end +end diff --git a/app/services/s3_storage_service.rb b/app/services/s3_storage_service.rb new file mode 100644 index 000000000..15cbee3d0 --- /dev/null +++ b/app/services/s3_storage_service.rb @@ -0,0 +1,78 @@ +class S3StorageService < StorageService + attr_reader :configuration + + def initialize(paas_config_service, paas_instance_name) + super() + @paas_config_service = paas_config_service + @paas_instance_name = (paas_instance_name || "").to_sym + @configuration = create_configuration + @client = create_client + end + + def list_files(folder) + @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder) + .flat_map { |response| response.contents.map(&:key) } + end + + def folder_present?(folder) + response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) + response.key_count == 1 + end + + def get_file_io(file_name) + @client.get_object(bucket: @configuration.bucket_name, key: file_name) + .body + end + + def write_file(file_name, data) + @client.put_object( + body: data, + bucket: @configuration.bucket_name, + key: file_name, + ) + end + +private + + def create_configuration + unless @paas_config_service.config_present? + raise "No PaaS configuration present" + end + unless @paas_config_service.s3_buckets.key?(@paas_instance_name) + raise "#{@paas_instance_name} instance name could not be found" + end + + bucket_config = @paas_config_service.s3_buckets[@paas_instance_name] + StorageConfiguration.new(bucket_config[:credentials]) + end + + def create_client + credentials = + Aws::Credentials.new( + @configuration.access_key_id, + @configuration.secret_access_key, + ) + Aws::S3::Client.new( + region: @configuration.region, + credentials:, + ) + end +end + +class StorageConfiguration + attr_reader :access_key_id, :secret_access_key, :bucket_name, :region + + def initialize(credentials) + @access_key_id = credentials[:aws_access_key_id] + @secret_access_key = credentials[:aws_secret_access_key] + @bucket_name = credentials[:bucket_name] + @region = credentials[:aws_region] + end + + def ==(other) + @access_key_id == other.access_key_id && + @secret_access_key == other.secret_access_key && + @bucket_name == other.bucket_name && + @region == other.region + end +end diff --git a/app/services/storage_service.rb b/app/services/storage_service.rb index 766af69fa..0135a92a1 100644 --- a/app/services/storage_service.rb +++ b/app/services/storage_service.rb @@ -1,77 +1,17 @@ class StorageService - attr_reader :configuration - - def initialize(paas_config_service, paas_instance_name) - @paas_config_service = paas_config_service - @paas_instance_name = (paas_instance_name || "").to_sym - @configuration = create_configuration - @client = create_client - end - - def list_files(folder) - @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder) - .flat_map { |response| response.contents.map(&:key) } - end - - def folder_present?(folder) - response = @client.list_objects_v2(bucket: @configuration.bucket_name, prefix: folder, max_keys: 1) - response.key_count == 1 - end - - def get_file_io(file_name) - @client.get_object(bucket: @configuration.bucket_name, key: file_name) - .body + def list_files(_folder) + raise NotImplementedError end - def write_file(file_name, data) - @client.put_object( - body: data, - bucket: @configuration.bucket_name, - key: file_name, - ) + def folder_present?(_folder) + raise NotImplementedError end -private - - def create_configuration - unless @paas_config_service.config_present? - raise "No PaaS configuration present" - end - unless @paas_config_service.s3_buckets.key?(@paas_instance_name) - raise "#{@paas_instance_name} instance name could not be found" - end - - bucket_config = @paas_config_service.s3_buckets[@paas_instance_name] - StorageConfiguration.new(bucket_config[:credentials]) - end - - def create_client - credentials = - Aws::Credentials.new( - @configuration.access_key_id, - @configuration.secret_access_key, - ) - Aws::S3::Client.new( - region: @configuration.region, - credentials:, - ) - end -end - -class StorageConfiguration - attr_reader :access_key_id, :secret_access_key, :bucket_name, :region - - def initialize(credentials) - @access_key_id = credentials[:aws_access_key_id] - @secret_access_key = credentials[:aws_secret_access_key] - @bucket_name = credentials[:bucket_name] - @region = credentials[:aws_region] + def get_file_io(_file_name) + raise NotImplementedError end - def ==(other) - @access_key_id == other.access_key_id && - @secret_access_key == other.secret_access_key && - @bucket_name == other.bucket_name && - @region == other.region + def write_file(_file_name, _data) + raise NotImplementedError end end diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake index 36a3a658c..cf669f488 100644 --- a/lib/tasks/data_export.rake +++ b/lib/tasks/data_export.rake @@ -4,7 +4,7 @@ namespace :core do format = args[:format] full_update = args[:full_update].present? && args[:full_update] == "true" - storage_service = StorageService.new(PaasConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"]) + storage_service = S3StorageService.new(PaasConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"]) export_service = Exports::CaseLogExportService.new(storage_service) if format.present? && format == "CSV" diff --git a/lib/tasks/data_import.rake b/lib/tasks/data_import.rake index 94553f0da..738fbceb5 100644 --- a/lib/tasks/data_import.rake +++ b/lib/tasks/data_import.rake @@ -5,7 +5,7 @@ namespace :core do path = args[:path] raise "Usage: rake core:data_import['data_type', 'path/to/xml_files']" if path.blank? || type.blank? - storage_service = StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + storage_service = S3StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) case type when "organisation" diff --git a/lib/tasks/data_import_field.rake b/lib/tasks/data_import_field.rake index c39afec8e..7de94cd09 100644 --- a/lib/tasks/data_import_field.rake +++ b/lib/tasks/data_import_field.rake @@ -5,7 +5,7 @@ namespace :core do path = args[:path] raise "Usage: rake core:data_import_field['field','path/to/xml_files']" if path.blank? || field.blank? - storage_service = StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + storage_service = S3StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) # We only allow a reduced list of known fields to be updatable case field diff --git a/lib/tasks/full_import.rake b/lib/tasks/full_import.rake index 42ac083f1..792eaceff 100644 --- a/lib/tasks/full_import.rake +++ b/lib/tasks/full_import.rake @@ -2,11 +2,13 @@ Import = Struct.new("Import", :import_class, :import_method, :folder) namespace :core do desc "Import all data XMLs from legacy CORE" - task :full_import, %i[path] => :environment do |_task, args| - path = args[:path] - raise "Usage: rake core:full_import['path/to/main_folder']" if path.blank? + task :full_import, %i[archive_path] => :environment do |_task, args| + archive_path = args[:archive_path] + raise "Usage: rake core:full_import['path/to/archive']" if archive_path.blank? - storage_service = StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + s3_service = S3StorageService.new(PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + archive_io = s3_service.get_file_io(archive_path) + archive_service = ArchiveStorageService.new(archive_io) import_list = [ Import.new(Imports::OrganisationImportService, :create_organisations, "institution"), @@ -18,12 +20,11 @@ namespace :core do Import.new(Imports::CaseLogsImportService, :create_logs, "logs"), ] - import_list.each do |import| - folder_path = File.join(path, import.folder, "") - if storage_service.folder_present?(folder_path) - import.import_class.new(storage_service).send(import.import_method, folder_path) + import_list.each do |step| + if archive_service.folder_present?(step.folder) + step.import_class.new(archive_service).send(step.import_method, step.folder) else - Rails.logger.info("#{folder_path} does not exist, skipping #{import.import_class}") + Rails.logger.info("#{step.folder} does not exist, skipping #{step.import_class}") end end end diff --git a/spec/fixtures/imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml b/spec/fixtures/imports/dataprotect/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml similarity index 100% rename from spec/fixtures/imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml rename to spec/fixtures/imports/dataprotect/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml diff --git a/spec/fixtures/imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml b/spec/fixtures/imports/institution/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml similarity index 100% rename from spec/fixtures/imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml rename to spec/fixtures/imports/institution/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml diff --git a/spec/fixtures/imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml b/spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml similarity index 100% rename from spec/fixtures/imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml rename to spec/fixtures/imports/logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml diff --git a/spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml b/spec/fixtures/imports/logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml similarity index 100% rename from spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml rename to spec/fixtures/imports/logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml diff --git a/spec/fixtures/imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml b/spec/fixtures/imports/logs/0ead17cb-1668-442d-898c-0d52879ff592.xml similarity index 100% rename from spec/fixtures/imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml rename to spec/fixtures/imports/logs/0ead17cb-1668-442d-898c-0d52879ff592.xml diff --git a/spec/fixtures/imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml b/spec/fixtures/imports/logs/166fc004-392e-47a8-acb8-1c018734882b.xml similarity index 100% rename from spec/fixtures/imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml rename to spec/fixtures/imports/logs/166fc004-392e-47a8-acb8-1c018734882b.xml diff --git a/spec/fixtures/imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml b/spec/fixtures/imports/logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml similarity index 100% rename from spec/fixtures/imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml rename to spec/fixtures/imports/logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml diff --git a/spec/fixtures/imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml b/spec/fixtures/imports/logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml similarity index 100% rename from spec/fixtures/imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml rename to spec/fixtures/imports/logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml diff --git a/spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml b/spec/fixtures/imports/mgmtgroups/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml similarity index 100% rename from spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml rename to spec/fixtures/imports/mgmtgroups/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml diff --git a/spec/fixtures/imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml b/spec/fixtures/imports/rent-period/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml similarity index 100% rename from spec/fixtures/imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml rename to spec/fixtures/imports/rent-period/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml diff --git a/spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml b/spec/fixtures/imports/schemes/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml similarity index 100% rename from spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml rename to spec/fixtures/imports/schemes/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml diff --git a/spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml b/spec/fixtures/imports/schemes/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml similarity index 100% rename from spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml rename to spec/fixtures/imports/schemes/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml diff --git a/spec/fixtures/imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml b/spec/fixtures/imports/user/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml similarity index 100% rename from spec/fixtures/imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml rename to spec/fixtures/imports/user/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml diff --git a/spec/fixtures/imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml b/spec/fixtures/imports/user/9ed81a262215a1634f0809effa683e38924d8bcb.xml similarity index 100% rename from spec/fixtures/imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml rename to spec/fixtures/imports/user/9ed81a262215a1634f0809effa683e38924d8bcb.xml diff --git a/spec/fixtures/imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml b/spec/fixtures/imports/user/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml similarity index 100% rename from spec/fixtures/imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml rename to spec/fixtures/imports/user/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml diff --git a/spec/fixtures/imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml b/spec/fixtures/imports/user/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml similarity index 100% rename from spec/fixtures/imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml rename to spec/fixtures/imports/user/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml diff --git a/spec/fixtures/imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml b/spec/fixtures/imports/user/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml similarity index 100% rename from spec/fixtures/imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml rename to spec/fixtures/imports/user/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml diff --git a/spec/fixtures/imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml b/spec/fixtures/imports/user/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml similarity index 100% rename from spec/fixtures/imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml rename to spec/fixtures/imports/user/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml diff --git a/spec/lib/tasks/data_export_spec.rb b/spec/lib/tasks/data_export_spec.rb index 69c70234c..ae0acdb0d 100644 --- a/spec/lib/tasks/data_export_spec.rb +++ b/spec/lib/tasks/data_export_spec.rb @@ -5,7 +5,7 @@ describe "rake core:data_export", type: task do subject(:task) { Rake::Task["core:data_export"] } let(:paas_instance) { "paas_export_instance" } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:paas_config_service) { instance_double(PaasConfigurationService) } let(:export_service) { instance_double(Exports::CaseLogExportService) } @@ -14,7 +14,7 @@ describe "rake core:data_export", type: task do Rake::Task.define_task(:environment) task.reenable - allow(StorageService).to receive(:new).and_return(storage_service) + allow(S3StorageService).to receive(:new).and_return(storage_service) allow(PaasConfigurationService).to receive(:new).and_return(paas_config_service) allow(Exports::CaseLogExportService).to receive(:new).and_return(export_service) allow(ENV).to receive(:[]) @@ -23,7 +23,7 @@ describe "rake core:data_export", type: task do context "when exporting case logs with no parameters" do it "starts the XML export process" do - expect(StorageService).to receive(:new).with(paas_config_service, paas_instance) + expect(S3StorageService).to receive(:new).with(paas_config_service, paas_instance) expect(Exports::CaseLogExportService).to receive(:new).with(storage_service) expect(export_service).to receive(:export_xml_case_logs) @@ -33,7 +33,7 @@ describe "rake core:data_export", type: task do context "when exporting case logs with CSV format" do it "starts the CSV export process" do - expect(StorageService).to receive(:new).with(paas_config_service, paas_instance) + expect(S3StorageService).to receive(:new).with(paas_config_service, paas_instance) expect(Exports::CaseLogExportService).to receive(:new).with(storage_service) expect(export_service).to receive(:export_csv_case_logs) diff --git a/spec/lib/tasks/data_import_spec.rb b/spec/lib/tasks/data_import_spec.rb index 1d49e6c70..9a88ef22e 100644 --- a/spec/lib/tasks/data_import_spec.rb +++ b/spec/lib/tasks/data_import_spec.rb @@ -5,7 +5,7 @@ describe "rake core:data_import", type: :task do subject(:task) { Rake::Task["core:data_import"] } let(:instance_name) { "paas_import_instance" } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:paas_config_service) { instance_double(PaasConfigurationService) } before do @@ -13,7 +13,7 @@ describe "rake core:data_import", type: :task do Rake::Task.define_task(:environment) task.reenable - allow(StorageService).to receive(:new).and_return(storage_service) + allow(S3StorageService).to receive(:new).and_return(storage_service) allow(PaasConfigurationService).to receive(:new).and_return(paas_config_service) allow(ENV).to receive(:[]) allow(ENV).to receive(:[]).with("IMPORT_PAAS_INSTANCE").and_return(instance_name) @@ -29,7 +29,7 @@ describe "rake core:data_import", type: :task do end it "creates an organisation from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::OrganisationImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_organisations).with(fixture_path) @@ -47,7 +47,7 @@ describe "rake core:data_import", type: :task do end it "creates a user from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::UserImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_users).with(fixture_path) @@ -65,7 +65,7 @@ describe "rake core:data_import", type: :task do end it "creates an organisation from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::DataProtectionConfirmationImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_data_protection_confirmations).with(fixture_path) @@ -83,7 +83,7 @@ describe "rake core:data_import", type: :task do end it "creates an organisation la from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::OrganisationRentPeriodImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_organisation_rent_periods).with(fixture_path) @@ -101,7 +101,7 @@ describe "rake core:data_import", type: :task do end it "creates case logs from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::CaseLogsImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_logs).with(fixture_path) @@ -119,7 +119,7 @@ describe "rake core:data_import", type: :task do end it "creates a scheme from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::SchemeImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_schemes).with(fixture_path) @@ -137,7 +137,7 @@ describe "rake core:data_import", type: :task do end it "creates a scheme location from the given XML file" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) expect(Imports::SchemeLocationImportService).to receive(:new).with(storage_service) expect(import_service).to receive(:create_scheme_locations).with(fixture_path) diff --git a/spec/lib/tasks/date_import_field_spec.rb b/spec/lib/tasks/date_import_field_spec.rb index a5a74b2e8..5dd96ff28 100644 --- a/spec/lib/tasks/date_import_field_spec.rb +++ b/spec/lib/tasks/date_import_field_spec.rb @@ -5,7 +5,7 @@ describe "rake core:data_import_field", type: :task do subject(:task) { Rake::Task["core:data_import_field"] } let(:instance_name) { "paas_import_instance" } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:paas_config_service) { instance_double(PaasConfigurationService) } before do @@ -13,7 +13,7 @@ describe "rake core:data_import_field", type: :task do Rake::Task.define_task(:environment) task.reenable - allow(StorageService).to receive(:new).and_return(storage_service) + allow(S3StorageService).to receive(:new).and_return(storage_service) allow(PaasConfigurationService).to receive(:new).and_return(paas_config_service) allow(ENV).to receive(:[]) allow(ENV).to receive(:[]).with("IMPORT_PAAS_INSTANCE").and_return(instance_name) @@ -32,7 +32,7 @@ describe "rake core:data_import_field", type: :task do let(:field) { "tenant_code" } it "properly configures the storage service" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) task.invoke(field, fixture_path) end @@ -46,7 +46,7 @@ describe "rake core:data_import_field", type: :task do let(:field) { "lettings_allocation" } it "properly configures the storage service" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) task.invoke(field, fixture_path) end @@ -60,7 +60,7 @@ describe "rake core:data_import_field", type: :task do let(:field) { "major_repairs" } it "properly configures the storage service" do - expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(S3StorageService).to receive(:new).with(paas_config_service, instance_name) task.invoke(field, fixture_path) end diff --git a/spec/lib/tasks/full_import_spec.rb b/spec/lib/tasks/full_import_spec.rb index 360fb8516..c7fedc6dd 100644 --- a/spec/lib/tasks/full_import_spec.rb +++ b/spec/lib/tasks/full_import_spec.rb @@ -1,11 +1,12 @@ require "rails_helper" require "rake" +require "zip" describe "rake core:full_import", type: :task do subject(:task) { Rake::Task["core:full_import"] } - let(:instance_name) { "paas_import_instance" } - let(:storage_service) { instance_double(StorageService) } + let(:s3_service) { instance_double(S3StorageService) } + let(:archive_service) { instance_double(ArchiveStorageService) } let(:paas_config_service) { instance_double(PaasConfigurationService) } before do @@ -13,14 +14,13 @@ describe "rake core:full_import", type: :task do Rake::Task.define_task(:environment) task.reenable - allow(StorageService).to receive(:new).and_return(storage_service) allow(PaasConfigurationService).to receive(:new).and_return(paas_config_service) - allow(ENV).to receive(:[]) - allow(ENV).to receive(:[]).with("IMPORT_PAAS_INSTANCE").and_return(instance_name) + allow(S3StorageService).to receive(:new).and_return(s3_service) + allow(s3_service).to receive(:get_file_io) + allow(ArchiveStorageService).to receive(:new).and_return(archive_service) end context "when starting a full import" do - let(:fixture_path) { "spec/fixtures/imports" } let(:case_logs_service) { instance_double(Imports::CaseLogsImportService) } let(:rent_period_service) { instance_double(Imports::OrganisationRentPeriodImportService) } let(:data_protection_service) { instance_double(Imports::DataProtectionConfirmationImportService) } @@ -44,16 +44,16 @@ describe "rake core:full_import", type: :task do end context "with all folders being present" do - before { allow(storage_service).to receive(:folder_present?).and_return(true) } + before { allow(archive_service).to receive(:folder_present?).and_return(true) } it "calls every import method with the correct folder" do - expect(organisation_service).to receive(:create_organisations).with("#{fixture_path}/institution/") - expect(scheme_service).to receive(:create_schemes).with("#{fixture_path}/mgmtgroups/") - expect(location_service).to receive(:create_scheme_locations).with("#{fixture_path}/schemes/") - expect(user_service).to receive(:create_users).with("#{fixture_path}/user/") - expect(data_protection_service).to receive(:create_data_protection_confirmations).with("#{fixture_path}/dataprotect/") - expect(rent_period_service).to receive(:create_organisation_rent_periods).with("#{fixture_path}/rent-period/") - expect(case_logs_service).to receive(:create_logs).with("#{fixture_path}/logs/") + expect(organisation_service).to receive(:create_organisations).with("institution") + expect(scheme_service).to receive(:create_schemes).with("mgmtgroups") + expect(location_service).to receive(:create_scheme_locations).with("schemes") + expect(user_service).to receive(:create_users).with("user") + expect(data_protection_service).to receive(:create_data_protection_confirmations).with("dataprotect") + expect(rent_period_service).to receive(:create_organisation_rent_periods).with("rent-period") + expect(case_logs_service).to receive(:create_logs).with("logs") task.invoke(fixture_path) end @@ -61,9 +61,9 @@ describe "rake core:full_import", type: :task do context "when a specific folders are missing" do before do - allow(storage_service).to receive(:folder_present?).and_return(true) - allow(storage_service).to receive(:folder_present?).with("#{fixture_path}/mgmtgroups/").and_return(false) - allow(storage_service).to receive(:folder_present?).with("#{fixture_path}/schemes/").and_return(false) + allow(archive_service).to receive(:folder_present?).and_return(true) + allow(archive_service).to receive(:folder_present?).with("mgmtgroups").and_return(false) + allow(archive_service).to receive(:folder_present?).with("schemes").and_return(false) end it "only calls import methods for existing folders" do @@ -75,8 +75,8 @@ describe "rake core:full_import", type: :task do expect(scheme_service).not_to receive(:create_schemes) expect(location_service).not_to receive(:create_scheme_locations) - expect(Rails.logger).to receive(:info).with("spec/fixtures/imports/mgmtgroups/ does not exist, skipping Imports::SchemeImportService") - expect(Rails.logger).to receive(:info).with("spec/fixtures/imports/schemes/ does not exist, skipping Imports::SchemeLocationImportService") + expect(Rails.logger).to receive(:info).with("mgmtgroups does not exist, skipping Imports::SchemeImportService") + expect(Rails.logger).to receive(:info).with("schemes does not exist, skipping Imports::SchemeLocationImportService") task.invoke(fixture_path) end diff --git a/spec/services/archive_storage_service_spec.rb b/spec/services/archive_storage_service_spec.rb new file mode 100644 index 000000000..fb33e4dd3 --- /dev/null +++ b/spec/services/archive_storage_service_spec.rb @@ -0,0 +1,65 @@ +require "rails_helper" + +RSpec.describe ArchiveStorageService do + subject(:archive_service) { described_class.new(archive_content) } + + let(:compressed_folder) { "my_directory" } + let(:compressed_filename) { "hello.txt" } + let(:compressed_filepath) { File.join(compressed_folder, compressed_filename) } + let(:compressed_file) do + file = Tempfile.new + file << "Hello World\n" + file.rewind + file + end + let(:archive_content) do + zip_file = Zip::File.open_buffer(StringIO.new) + zip_file.mkdir(compressed_folder) + zip_file.add(compressed_filepath, compressed_file) + zip_file.write_buffer + end + + describe "#list_files" do + it "returns the list of files present in an existing folder" do + file_list = archive_service.list_files(compressed_folder) + expect(file_list).to contain_exactly(compressed_filepath) + end + + it "returns an empty file list for an unknown folder" do + file_list = archive_service.list_files("random_folder") + expect(file_list).to be_empty + end + end + + describe "#folder_present?" do + it "returns true if a folder in the archive exists" do + presence = archive_service.folder_present?(compressed_folder) + expect(presence).to be_truthy + end + + it "returns false if a folder in the archive does not exist" do + presence = archive_service.folder_present?("random_folder") + expect(presence).to be_falsey + end + end + + describe "#get_file_io" do + it "returns the file content if a file exists" do + content = archive_service.get_file_io(compressed_filepath) + expect(content.read).to eq(compressed_file.read) + end + + it "raises an error if the file exists but is too large" do + archive = archive_service.instance_variable_get(:@archive) + allow(archive).to receive(:get_entry).and_return(Zip::Entry.new(nil, "", nil, nil, nil, nil, nil, 100_000_000, nil)) + + expect { archive_service.get_file_io(compressed_filepath) } + .to raise_error(RuntimeError, "File too large to be extracted") + end + + it "raises an error if a file does not exist" do + expect { archive_service.get_file_io("random.zzz") } + .to raise_error(Errno::ENOENT) + end + end +end diff --git a/spec/services/exports/case_log_export_service_spec.rb b/spec/services/exports/case_log_export_service_spec.rb index 72e4ee518..fc59cde4e 100644 --- a/spec/services/exports/case_log_export_service_spec.rb +++ b/spec/services/exports/case_log_export_service_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe Exports::CaseLogExportService do subject(:export_service) { described_class.new(storage_service) } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:xml_export_file) { File.open("spec/fixtures/exports/general_needs_log.xml", "r:UTF-8") } let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") } diff --git a/spec/services/imports/case_logs_field_import_service_spec.rb b/spec/services/imports/case_logs_field_import_service_spec.rb index 22c1371b0..7184ee648 100644 --- a/spec/services/imports/case_logs_field_import_service_spec.rb +++ b/spec/services/imports/case_logs_field_import_service_spec.rb @@ -3,11 +3,11 @@ require "rails_helper" RSpec.describe Imports::CaseLogsFieldImportService do subject(:import_service) { described_class.new(storage_service, logger) } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") } - let(:fixture_directory) { "spec/fixtures/imports/case_logs" } + let(:fixture_directory) { "spec/fixtures/imports/logs" } let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" } let(:case_log_file) { open_file(fixture_directory, case_log_id) } diff --git a/spec/services/imports/case_logs_import_service_spec.rb b/spec/services/imports/case_logs_import_service_spec.rb index d8ad458ee..30a5cc41e 100644 --- a/spec/services/imports/case_logs_import_service_spec.rb +++ b/spec/services/imports/case_logs_import_service_spec.rb @@ -3,12 +3,12 @@ require "rails_helper" RSpec.describe Imports::CaseLogsImportService do subject(:case_log_service) { described_class.new(storage_service, logger) } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") } let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json", "2022_2023") } - let(:fixture_directory) { "spec/fixtures/imports/case_logs" } + let(:fixture_directory) { "spec/fixtures/imports/logs" } let(:organisation) { FactoryBot.create(:organisation, old_visible_id: "1", provider_type: "PRP") } let(:scheme1) { FactoryBot.create(:scheme, old_visible_id: 123, owning_organisation: organisation) } diff --git a/spec/services/imports/data_protection_confirmation_import_service_spec.rb b/spec/services/imports/data_protection_confirmation_import_service_spec.rb index e14ce6b1a..db5a25046 100644 --- a/spec/services/imports/data_protection_confirmation_import_service_spec.rb +++ b/spec/services/imports/data_protection_confirmation_import_service_spec.rb @@ -1,11 +1,11 @@ require "rails_helper" RSpec.describe Imports::DataProtectionConfirmationImportService do - let(:fixture_directory) { "spec/fixtures/imports/data_protection_confirmations" } + let(:fixture_directory) { "spec/fixtures/imports/dataprotect" } let(:old_org_id) { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" } let(:old_id) { old_org_id } let(:import_file) { File.open("#{fixture_directory}/#{old_id}.xml") } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } context "when importing data protection confirmations" do diff --git a/spec/services/imports/organisation_import_service_spec.rb b/spec/services/imports/organisation_import_service_spec.rb index b91b566ef..5eceea549 100644 --- a/spec/services/imports/organisation_import_service_spec.rb +++ b/spec/services/imports/organisation_import_service_spec.rb @@ -1,11 +1,11 @@ require "rails_helper" RSpec.describe Imports::OrganisationImportService do - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(Rails::Rack::Logger) } let(:folder_name) { "organisations" } let(:filenames) { %w[my_folder/my_file1.xml my_folder/my_file2.xml] } - let(:fixture_directory) { "spec/fixtures/imports/organisations" } + let(:fixture_directory) { "spec/fixtures/imports/institution" } def create_organisation_file(fixture_directory, visible_id, name = nil) file = File.open("#{fixture_directory}/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml") diff --git a/spec/services/imports/organisation_rent_period_import_service_spec.rb b/spec/services/imports/organisation_rent_period_import_service_spec.rb index e15d10a22..b85bb64e5 100644 --- a/spec/services/imports/organisation_rent_period_import_service_spec.rb +++ b/spec/services/imports/organisation_rent_period_import_service_spec.rb @@ -1,11 +1,11 @@ require "rails_helper" RSpec.describe Imports::OrganisationRentPeriodImportService do - let(:fixture_directory) { "spec/fixtures/imports/organisation_rent_periods" } + let(:fixture_directory) { "spec/fixtures/imports/rent-period" } let(:old_org_id) { "44026acc7ed5c29516b26f2a5deb639e5e37966d" } let(:old_id) { "ebd22326d33e389e9f1bfd546979d2c05f9e68d6" } let(:import_file) { File.open("#{fixture_directory}/#{old_id}.xml") } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } context "when importing organisation rent periods" do diff --git a/spec/services/imports/scheme_import_service_spec.rb b/spec/services/imports/scheme_import_service_spec.rb index 63227dd8e..b6b98d25d 100644 --- a/spec/services/imports/scheme_import_service_spec.rb +++ b/spec/services/imports/scheme_import_service_spec.rb @@ -3,10 +3,10 @@ require "rails_helper" RSpec.describe Imports::SchemeImportService do subject(:scheme_service) { described_class.new(storage_service, logger) } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } - let(:fixture_directory) { "spec/fixtures/imports/schemes" } + let(:fixture_directory) { "spec/fixtures/imports/mgmtgroups" } let(:scheme_id) { "6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d" } let!(:owning_org) { FactoryBot.create(:organisation, old_org_id: "7c5bd5fb549c09z2c55d9cb90d7ba84927e64618") } diff --git a/spec/services/imports/scheme_location_import_service_spec.rb b/spec/services/imports/scheme_location_import_service_spec.rb index 7f9e33374..95a5b2b9e 100644 --- a/spec/services/imports/scheme_location_import_service_spec.rb +++ b/spec/services/imports/scheme_location_import_service_spec.rb @@ -3,10 +3,10 @@ require "rails_helper" RSpec.describe Imports::SchemeLocationImportService do subject(:location_service) { described_class.new(storage_service, logger) } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } - let(:fixture_directory) { "spec/fixtures/imports/scheme_locations" } + let(:fixture_directory) { "spec/fixtures/imports/schemes" } let(:first_location_id) { "0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e" } let(:second_location_id) { "0bb3836b70b4dd9903263d5a764a5c45b964a89d" } diff --git a/spec/services/imports/user_import_service_spec.rb b/spec/services/imports/user_import_service_spec.rb index 66ea6eb07..b9e3028c9 100644 --- a/spec/services/imports/user_import_service_spec.rb +++ b/spec/services/imports/user_import_service_spec.rb @@ -1,11 +1,11 @@ require "rails_helper" RSpec.describe Imports::UserImportService do - let(:fixture_directory) { "spec/fixtures/imports/users" } + let(:fixture_directory) { "spec/fixtures/imports/user" } let(:old_user_id) { "fc7625a02b24ae16162aa63ae7cb33feeec0c373" } let(:old_org_id) { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" } let(:user_file) { File.open("#{fixture_directory}/#{old_user_id}.xml") } - let(:storage_service) { instance_double(StorageService) } + let(:storage_service) { instance_double(S3StorageService) } let(:logger) { instance_double(ActiveSupport::Logger) } let(:notify_client) { instance_double(Notifications::Client) } let(:devise_notify_mailer) { DeviseNotifyMailer.new } diff --git a/spec/services/storage_service_spec.rb b/spec/services/s3_storage_service_spec.rb similarity index 99% rename from spec/services/storage_service_spec.rb rename to spec/services/s3_storage_service_spec.rb index d19d51ed8..c89b7c1bb 100644 --- a/spec/services/storage_service_spec.rb +++ b/spec/services/s3_storage_service_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe StorageService do +RSpec.describe S3StorageService do let(:instance_name) { "instance_1" } let(:bucket_name) { "bucket_1" } let(:vcap_services) do From 2ce610933d0dcb267049e5658a7e88474afc535d Mon Sep 17 00:00:00 2001 From: James Rose Date: Fri, 12 Aug 2022 11:23:37 +0100 Subject: [PATCH 23/29] Fix sign in validation error copy (#831) These errors were injecting devise's authentication_keys which includes email and was duplicating that copy, for example: "Incorrect email email or password" --- config/locales/devise.en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index e0babe522..16299ea10 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -9,10 +9,10 @@ en: failure: already_authenticated: "You are already signed in" inactive: "Your account has not been activated yet" - invalid: "Incorrect %{authentication_keys} email or password" + invalid: "Incorrect %{authentication_keys} or password" locked: "Your account has been locked." last_attempt: "You have one more attempt before your account is locked" - not_found_in_database: "Incorrect %{authentication_keys} email or password" + not_found_in_database: "Incorrect %{authentication_keys} or password" timeout: "Your session expired. Sign in again to continue." unauthenticated: "You need to sign in or sign up before continuing" unconfirmed: "You must confirm your email address before continuing" From 4ae1bfb8178b73d89c72722ba4089539842fb1e7 Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:32:16 +0100 Subject: [PATCH 24/29] CLDC-1433 Add parent child relationship model (#828) * Update remote schema.rb with migrations, main features for this ticket still incoming * Add join table for organisation self-referential parent/child relationships * Add join table for organisation self-referential parent/child relationships * Remove organisation_la factory and add test for organisation_relationship child/parent association * Update spec/models/organisation_spec.rb Co-authored-by: James Rose Co-authored-by: James Rose --- app/models/organisation.rb | 4 ++++ app/models/organisation_relationship.rb | 4 ++++ ...20810152340_add_parent_child_relationships.rb | 9 +++++++++ db/schema.rb | 9 ++++++++- spec/factories/organisation.rb | 12 +++++------- spec/models/organisation_spec.rb | 16 ++++++++++++++++ 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 app/models/organisation_relationship.rb create mode 100644 db/migrate/20220810152340_add_parent_child_relationships.rb diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 625c0bd05..57cf0a5a2 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -6,6 +6,10 @@ class Organisation < ApplicationRecord has_many :organisation_rent_periods has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all has_many :managed_schemes, class_name: "Scheme", foreign_key: "managing_organisation_id" + has_many :parent_organisation_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship" + has_many :parent_organisations, through: :parent_organisation_relationships + has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" + has_many :child_organisations, through: :child_organisation_relationships scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by, ->(param) { search_by_name(param) } diff --git a/app/models/organisation_relationship.rb b/app/models/organisation_relationship.rb new file mode 100644 index 000000000..034fc5d0e --- /dev/null +++ b/app/models/organisation_relationship.rb @@ -0,0 +1,4 @@ +class OrganisationRelationship < ApplicationRecord + belongs_to :child_organisation, class_name: "Organisation" + belongs_to :parent_organisation, class_name: "Organisation" +end diff --git a/db/migrate/20220810152340_add_parent_child_relationships.rb b/db/migrate/20220810152340_add_parent_child_relationships.rb new file mode 100644 index 000000000..49891f5ec --- /dev/null +++ b/db/migrate/20220810152340_add_parent_child_relationships.rb @@ -0,0 +1,9 @@ +class AddParentChildRelationships < ActiveRecord::Migration[7.0] + def change + create_table :organisation_relationships do |t| + t.integer :child_organisation_id, foreign_key: true + t.integer :parent_organisation_id, foreign_key: true + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0baafd421..1b65e5482 100644 --- a/db/schema.rb +++ b/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: 2022_08_02_125711) do +ActiveRecord::Schema[7.0].define(version: 2022_08_10_152340) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -263,6 +263,13 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_02_125711) do t.boolean "empty_export", default: false, null: false end + create_table "organisation_relationships", force: :cascade do |t| + t.integer "child_organisation_id" + t.integer "parent_organisation_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "organisation_rent_periods", force: :cascade do |t| t.bigint "organisation_id" t.integer "rent_period" diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb index 5ce6cfc03..8b02f030a 100644 --- a/spec/factories/organisation.rb +++ b/spec/factories/organisation.rb @@ -11,17 +11,15 @@ FactoryBot.define do holds_own_stock { true } end - factory :organisation_la do - organisation - ons_code { "E07000041" } - created_at { Time.zone.now } - updated_at { Time.zone.now } - end - factory :organisation_rent_period do organisation rent_period { 2 } created_at { Time.zone.now } updated_at { Time.zone.now } end + + factory :organisation_relationship do + child_organisation { FactoryBot.create(:organisation) } + parent_organisation { FactoryBot.create(:organisation) } + end end diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index fd2088b6d..3adcf0a64 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -27,6 +27,22 @@ RSpec.describe Organisation, type: :model do .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type #{I18n.t('validations.organisation.provider_type_missing')}") end + context "with parent/child association" do + let(:child_organisation) { FactoryBot.create(:organisation, name: "DLUHC Child") } + + before do + FactoryBot.create(:organisation_relationship, child_organisation:, parent_organisation: organisation) + end + + it "has correct child" do + expect(organisation.child_organisations.first).to eq(child_organisation) + end + + it "has correct parent" do + expect(child_organisation.parent_organisations.first).to eq(organisation) + end + end + context "with data protection confirmations" do before do FactoryBot.create(:data_protection_confirmation, organisation:, confirmed: false, created_at: Time.utc(2018, 0o6, 0o5, 10, 36, 49)) From bf7c81592dbe810a551404a51c13158ea1044437 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:24:48 +0100 Subject: [PATCH 25/29] More robust date validation (#830) * More robust date validation * Update app/controllers/form_controller.rb Co-authored-by: James Rose Co-authored-by: James Rose --- app/controllers/form_controller.rb | 2 +- spec/requests/form_controller_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index ad3f858e3..52e3bc3a7 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -89,7 +89,7 @@ private year = params["case_log"]["#{question.id}(1i)"] next unless [day, month, year].any?(&:present?) - result[question.id] = if day.to_i.between?(1, 31) && month.to_i.between?(1, 12) && year.to_i.between?(2000, 2200) + result[question.id] = if Date.valid_date?(year.to_i, month.to_i, day.to_i) && year.to_i.between?(2000, 2200) Date.new(year.to_i, month.to_i, day.to_i) else Date.new(0, 1, 1) diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index 7bad7b0a3..4be3f5c03 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -255,6 +255,27 @@ RSpec.describe FormController, type: :request do expect(Rails.logger).to receive(:info).with("User triggered validation(s) on: age1").once post "/logs/#{case_log.id}/form", params: params end + + context "when the number of days is too high for the month" do + let(:page_id) { "tenancy_start_date" } + let(:params) do + { + id: case_log.id, + case_log: { + page: page_id, + "startdate(3i)" => 31, + "startdate(2i)" => 6, + "startdate(1i)" => 2022, + }, + } + end + + it "validates the date correctly" do + post "/logs/#{case_log.id}/form", params: params + follow_redirect! + expect(page).to have_content("There is a problem") + end + end end context "with valid answers" do From a70c90e4858f8ed93fb07782e4e064017af71d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Meny?= Date: Tue, 16 Aug 2022 09:49:04 +0100 Subject: [PATCH 26/29] CLDC-1221: Handles the confirm status during import (#834) * Handles the confirm status during import * Typo --- .../imports/scheme_location_import_service.rb | 22 ++++++++++++++----- lib/tasks/full_import.rake | 1 + spec/lib/tasks/full_import_spec.rb | 1 + .../scheme_location_import_service_spec.rb | 15 +++++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/services/imports/scheme_location_import_service.rb b/app/services/imports/scheme_location_import_service.rb index 17f96d33d..cdcd9448d 100644 --- a/app/services/imports/scheme_location_import_service.rb +++ b/app/services/imports/scheme_location_import_service.rb @@ -27,7 +27,7 @@ module Imports }.freeze def create_scheme(source_scheme, attributes) - Scheme.create!( + scheme = Scheme.new( scheme_type: attributes["scheme_type"], registered_under_care_act: attributes["registered_under_care_act"], support_type: attributes["support_type"], @@ -36,7 +36,6 @@ module Imports secondary_client_group: attributes["secondary_client_group"], sensitive: attributes["sensitive"], end_date: attributes["end_date"], - confirmed: true, # These values were set by the scheme import (management groups) owning_organisation_id: source_scheme.owning_organisation_id, managing_organisation_id: source_scheme.managing_organisation_id, @@ -45,10 +44,12 @@ module Imports old_id: source_scheme.old_id, old_visible_id: source_scheme.old_visible_id, ) + confirm_scheme(scheme) + scheme.save! && scheme end def update_scheme(scheme, attributes) - scheme.update!( + scheme.attributes = { scheme_type: attributes["scheme_type"], registered_under_care_act: attributes["registered_under_care_act"], support_type: attributes["support_type"], @@ -57,9 +58,18 @@ module Imports secondary_client_group: attributes["secondary_client_group"], sensitive: attributes["sensitive"], end_date: attributes["end_date"], - confirmed: true, - ) - scheme + } + confirm_scheme(scheme) + scheme.save! && scheme + end + + def confirm_scheme(scheme) + scheme.confirmed = true + scheme.validate_confirmed + unless scheme.errors.empty? + scheme.confirmed = false + scheme.errors.clear + end end def scheme_attributes(xml_doc) diff --git a/lib/tasks/full_import.rake b/lib/tasks/full_import.rake index 792eaceff..1c082b460 100644 --- a/lib/tasks/full_import.rake +++ b/lib/tasks/full_import.rake @@ -22,6 +22,7 @@ namespace :core do import_list.each do |step| if archive_service.folder_present?(step.folder) + Rails.logger.info("Start importing folder #{step.folder}") step.import_class.new(archive_service).send(step.import_method, step.folder) else Rails.logger.info("#{step.folder} does not exist, skipping #{step.import_class}") diff --git a/spec/lib/tasks/full_import_spec.rb b/spec/lib/tasks/full_import_spec.rb index c7fedc6dd..99f95f555 100644 --- a/spec/lib/tasks/full_import_spec.rb +++ b/spec/lib/tasks/full_import_spec.rb @@ -64,6 +64,7 @@ describe "rake core:full_import", type: :task do allow(archive_service).to receive(:folder_present?).and_return(true) allow(archive_service).to receive(:folder_present?).with("mgmtgroups").and_return(false) allow(archive_service).to receive(:folder_present?).with("schemes").and_return(false) + allow(Rails.logger).to receive(:info) end it "only calls import methods for existing folders" do diff --git a/spec/services/imports/scheme_location_import_service_spec.rb b/spec/services/imports/scheme_location_import_service_spec.rb index 95a5b2b9e..ebe421f45 100644 --- a/spec/services/imports/scheme_location_import_service_spec.rb +++ b/spec/services/imports/scheme_location_import_service_spec.rb @@ -157,6 +157,7 @@ RSpec.describe Imports::SchemeLocationImportService do expect(location.scheme.secondary_client_group).to be_nil expect(location.scheme.sensitive).to eq("No") expect(location.scheme.end_date).to eq("2050-12-31") + expect(location.scheme.confirmed).to be_truthy end context "and the end date is before the current date" do @@ -183,5 +184,19 @@ RSpec.describe Imports::SchemeLocationImportService do .not_to change(Location, :count) end end + + context "and the registered under care act value is missing" do + before { location_xml.at_xpath("//scheme:reg-home-type").content = "0" } + + it "sets the registered under care act to nil" do + location = location_service.create_scheme_location(location_xml) + expect(location.scheme.registered_under_care_act).to be_nil + end + + it "sets the confirmed status to false" do + location = location_service.create_scheme_location(location_xml) + expect(location.scheme.confirmed).to be_falsey + end + end end end From d3e1f19bce5204a328a7a1dd259e36e29e0df97a Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 19 Aug 2022 10:38:28 +0100 Subject: [PATCH 27/29] Cldc 1432 csv labels and codes (#836) * remove irrelevant fields from csv based on the user * display yes/no instead of true/false * display organisation and user names instead of ids * refactor tests --- app/controllers/case_logs_controller.rb | 2 +- app/models/case_log.rb | 34 ++++++++++++++++--- spec/fixtures/files/case_logs_download.csv | 4 +-- .../files/case_logs_download_non_support.csv | 2 ++ spec/models/case_log_spec.rb | 24 +++++++++---- 5 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 spec/fixtures/files/case_logs_download_non_support.csv diff --git a/app/controllers/case_logs_controller.rb b/app/controllers/case_logs_controller.rb index 740dd2003..17bae5ee8 100644 --- a/app/controllers/case_logs_controller.rb +++ b/app/controllers/case_logs_controller.rb @@ -22,7 +22,7 @@ class CaseLogsController < ApplicationController end format.csv do - send_data unpaginated_filtered_logs.to_csv, filename: "logs-#{Time.zone.now}.csv" + send_data unpaginated_filtered_logs.to_csv(current_user), filename: "logs-#{Time.zone.now}.csv" end end end diff --git a/app/models/case_log.rb b/app/models/case_log.rb index be31c9200..4f4e535a3 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -74,6 +74,7 @@ class CaseLog < ApplicationRecord NUM_OF_WEEKS_FROM_PERIOD = { 2 => 26, 3 => 13, 4 => 12, 5 => 50, 6 => 49, 7 => 48, 8 => 47, 9 => 46, 1 => 52 }.freeze SUFFIX_FROM_PERIOD = { 2 => "every 2 weeks", 3 => "every 4 weeks", 4 => "every month" }.freeze RETIREMENT_AGES = { "M" => 67, "F" => 60, "X" => 67 }.freeze + CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check sale_or_letting first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays].freeze enum status: STATUS def form @@ -426,18 +427,43 @@ class CaseLog < ApplicationRecord [30, 31].any?(prevten) end - def self.to_csv + def owning_organisation_name + owning_organisation&.name + end + + def managing_organisation_name + managing_organisation&.name + end + + def created_by_name + created_by&.name + end + + def self.to_csv(user = nil) CSV.generate(headers: true) do |csv| - csv << attribute_names + %w[unittype_sh] + attributes = csv_attributes(user) + csv << attributes all.find_each do |record| - csv << record.attributes.merge({ "unittype_sh" => record.unittype_sh, "la" => record.la }).map do |att, val| - record.form.get_question(att, record)&.label_from_value(val) || val + csv << attributes.map do |att| + record.form.get_question(att, record)&.label_from_value(record.send(att)) || label_from_value(record.send(att)) end end end end + def self.label_from_value(value) + return "Yes" if value == true + return "No" if value == false + + value + end + + def self.csv_attributes(user) + attributes = attribute_names - %w[owning_organisation_id managing_organisation_id created_by_id] + %w[unittype_sh owning_organisation_name managing_organisation_name created_by_name] + user.present? && !user.support? ? attributes - CSV_FIELDS_TO_OMIT : attributes + end + def soft_min_for_period soft_min = LaRentRange.find_by(start_year: collection_start_year, la:, beds:, lettype:).soft_min "#{soft_value_for_period(soft_min)} #{SUFFIX_FROM_PERIOD[period].presence || 'every week'}" diff --git a/spec/fixtures/files/case_logs_download.csv b/spec/fixtures/files/case_logs_download.csv index 9af96e3df..5f5a1c96f 100644 --- a/spec/fixtures/files/case_logs_download.csv +++ b/spec/fixtures/files/case_logs_download.csv @@ -1,2 +1,2 @@ -id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,net_income_value_check,property_owner_organisation,property_manager_organisation,sale_or_letting,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,owning_organisation_id,managing_organisation_id,renttype,needstype,lettype,postcode_known,is_la_inferred,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,previous_la_known,is_previous_la_inferred,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,ethnic_other,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,rent_type,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,created_by_id,illness_type_0,retirement_value_check,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,scheme_id,location_id,unittype_sh -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,DLUHC,{owning_org_id},,Supported housing,,,false,0,0,0,,0,,,,,,,,,,,,,,false,,,,,,,,,,,,,,,,,,,,0,,,,,,,,0,,,,,,,,,,,,,,,,,Danny Rojas,,,,,,9,,,{scheme_id},SE1 1TE,6 +id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,net_income_value_check,property_owner_organisation,property_manager_organisation,sale_or_letting,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,needstype,lettype,postcode_known,is_la_inferred,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,previous_la_known,is_previous_la_inferred,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,ethnic_other,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,rent_type,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,illness_type_0,retirement_value_check,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,scheme_id,location_id,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,,Supported housing,,,No,0,0,0,,0,,,,,,,,,,,,,,No,,,,,,,,,,,,,,,,,,,,0,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,9,,,{scheme_id},SE1 1TE,6,DLUHC,DLUHC,Danny Rojas diff --git a/spec/fixtures/files/case_logs_download_non_support.csv b/spec/fixtures/files/case_logs_download_non_support.csv new file mode 100644 index 000000000..9bcd0e6aa --- /dev/null +++ b/spec/fixtures/files/case_logs_download_non_support.csv @@ -0,0 +1,2 @@ +id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,ethnic_group,ethnic_other,has_benefits,renewal,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,illness_type_0,sheltered,scheme_id,location_id,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,0,,,,,,,,,,,,,,0,,0,,,,,,,,,,,,,,,,{scheme_id},SE1 1TE,6,DLUHC,DLUHC,Danny Rojas diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index 704879df5..fbf7643ee 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -2293,22 +2293,32 @@ RSpec.describe CaseLog do end describe "csv download" do - let(:csv_export_file) { File.open("spec/fixtures/files/case_logs_download.csv", "r:UTF-8") } let(:scheme) { FactoryBot.create(:scheme) } let(:location) { FactoryBot.create(:location, :export, scheme:, type_of_unit: 6, postcode: "SE11TE") } let(:user) { FactoryBot.create(:user, organisation: location.scheme.owning_organisation) } + let(:expected_content) { csv_export_file.read } before do Timecop.freeze(Time.utc(2022, 6, 5)) - end - - it "generates a correct csv from a case log" do case_log = FactoryBot.create(:case_log, needstype: 2, scheme:, location:, owning_organisation: scheme.owning_organisation, created_by: user) - expected_content = csv_export_file.read expected_content.sub!(/\{id\}/, case_log["id"].to_s) - expected_content.sub!(/\{owning_org_id\}/, case_log["owning_organisation_id"].to_s) expected_content.sub!(/\{scheme_id\}/, scheme["service_name"].to_s) - expect(described_class.to_csv).to eq(expected_content) + end + + context "with a support user" do + let(:csv_export_file) { File.open("spec/fixtures/files/case_logs_download.csv", "r:UTF-8") } + + it "generates a correct csv from a case log" do + expect(described_class.to_csv).to eq(expected_content) + end + end + + context "with a non support user" do + let(:csv_export_file) { File.open("spec/fixtures/files/case_logs_download_non_support.csv", "r:UTF-8") } + + it "generates a correct csv from a case log" do + expect(described_class.to_csv(user)).to eq(expected_content) + end end end end From 0ff0aa31fe2b14451413cf63b9213657ae3fece5 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:28:37 +0100 Subject: [PATCH 28/29] Cldc 1338 dates validation (#820) * Change property major repairs date validation from 2 years to 10 * add voiddate_date_in_soft_range? and major_repairs_date_in_soft_range? methods * update error messages for hard validations * Add property_major_repairs_value_check to the form * add void_date_value_check and fix some namings * Update value_check hidden in check answers to depend on whether the questions is answered * Remove a schema key * extract constants for number of days * change error messages wording * update schema and csv files --- app/models/validations/date_validations.rb | 4 +- app/models/validations/soft_validations.rb | 11 + .../imports/case_logs_import_service.rb | 4 +- config/forms/2021_2022.json | 323 +++++++++++++++--- config/forms/2022_2023.json | 323 +++++++++++++++--- config/locales/en.yml | 6 +- ...0330_add_major_repairs_date_value_check.rb | 5 + ...0220808093035_add_void_date_value_check.rb | 5 + db/schema.rb | 2 + spec/factories/case_log.rb | 2 + spec/fixtures/files/case_logs_download.csv | 4 +- .../files/case_logs_download_non_support.csv | 4 +- .../validations/date_validations_spec.rb | 10 +- .../validations/soft_validations_spec.rb | 32 ++ 14 files changed, 642 insertions(+), 93 deletions(-) create mode 100644 db/migrate/20220808090330_add_major_repairs_date_value_check.rb create mode 100644 db/migrate/20220808093035_add_void_date_value_check.rb diff --git a/app/models/validations/date_validations.rb b/app/models/validations/date_validations.rb index 774f9828c..6351a712b 100644 --- a/app/models/validations/date_validations.rb +++ b/app/models/validations/date_validations.rb @@ -9,8 +9,8 @@ module Validations::DateValidations record.errors.add :mrcdate, I18n.t("validations.property.mrcdate.not_first_let") end - if record["mrcdate"].present? && record["startdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 730 - record.errors.add :mrcdate, I18n.t("validations.property.mrcdate.730_days_before_tenancy_start") + if record["mrcdate"].present? && record["startdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 3650 + record.errors.add :mrcdate, I18n.t("validations.property.mrcdate.ten_years_before_tenancy_start") end end diff --git a/app/models/validations/soft_validations.rb b/app/models/validations/soft_validations.rb index 686c69174..dd3e94b2e 100644 --- a/app/models/validations/soft_validations.rb +++ b/app/models/validations/soft_validations.rb @@ -62,6 +62,17 @@ module Validations::SoftValidations end end + TWO_YEARS_IN_DAYS = 730 + TEN_YEARS_IN_DAYS = 3650 + + def major_repairs_date_in_soft_range? + mrcdate.present? && startdate.present? && mrcdate.between?(startdate.to_date - TEN_YEARS_IN_DAYS, startdate.to_date - TWO_YEARS_IN_DAYS) + end + + def voiddate_in_soft_range? + voiddate.present? && startdate.present? && voiddate.between?(startdate.to_date - TEN_YEARS_IN_DAYS, startdate.to_date - TWO_YEARS_IN_DAYS) + end + private def details_known_or_lead_tenant?(tenant_number) diff --git a/app/services/imports/case_logs_import_service.rb b/app/services/imports/case_logs_import_service.rb index 96811487f..a0d75c0bb 100644 --- a/app/services/imports/case_logs_import_service.rb +++ b/app/services/imports/case_logs_import_service.rb @@ -209,6 +209,8 @@ module Imports # Soft validations can become required answers, set them to yes by default attributes["pregnancy_value_check"] = 0 + attributes["major_repairs_date_value_check"] = 0 + attributes["void_date_value_check"] = 0 attributes["retirement_value_check"] = 0 attributes["rent_value_check"] = 0 attributes["net_income_value_check"] = 0 @@ -273,7 +275,7 @@ module Imports end def fields_not_present_in_softwire_data - %w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check] + %w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check major_repairs_date_value_check void_date_value_check] end def check_status_completed(case_log, previous_status) diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index ffbdc0887..d365a5a8f 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -771,6 +771,38 @@ } ] }, + "void_date_value_check": { + "depends_on": [{ "voiddate_in_soft_range?": true }], + "title_text": { + "translation": "soft_validations.void_date.title_text" + }, + "informative_text": {}, + "questions": { + "void_date_value_check": { + "check_answer_label": "Void date confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "void_date_value_check": 0 + }, + { + "void_date_value_check": 1 + } + ] + }, + "header": "Are you sure the time between these dates is correct?", + "type": "interruption_screen", + "answer_options": { + "0": { + "value": "Yes" + }, + "1": { + "value": "No" + } + } + } + } + }, "new_build_handover_date": { "header": "", "description": "", @@ -867,6 +899,38 @@ "rsnvac": 19 } ] + }, + "property_major_repairs_value_check": { + "depends_on": [{ "major_repairs_date_in_soft_range?": true }], + "title_text": { + "translation": "soft_validations.major_repairs_date.title_text" + }, + "informative_text": {}, + "questions": { + "major_repairs_date_value_check": { + "check_answer_label": "Major repairs date confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "major_repairs_date_value_check": 0 + }, + { + "major_repairs_date_value_check": 1 + } + ] + }, + "header": "Are you sure the time between these dates is correct?", + "type": "interruption_screen", + "answer_options": { + "0": { + "value": "Yes" + }, + "1": { + "value": "No" + } + } + } + } } } }, @@ -1722,8 +1786,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -1768,8 +1841,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -2216,8 +2298,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -2262,8 +2353,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -2707,8 +2807,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -2753,8 +2862,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -3195,8 +3313,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -3241,8 +3368,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -3680,8 +3816,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -3726,8 +3871,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -4162,8 +4316,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -4208,8 +4371,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -4641,8 +4813,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -4687,8 +4868,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -5117,8 +5307,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -5163,8 +5362,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -5381,8 +5589,16 @@ }, "questions": { "pregnancy_value_check": { - "check_answer_label": "Pregnancy soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Pregnancy confirmation", + "hidden_in_check_answers": { + "depends_on": [{ + "pregnancy_value_check": 0 + }, + { + "pregnancy_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -6875,8 +7091,17 @@ }, "questions": { "net_income_value_check": { - "check_answer_label": "Net income soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Net income confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "net_income_value_check": 0 + }, + { + "net_income_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -7779,8 +8004,17 @@ }, "questions": { "rent_value_check": { - "check_answer_label": "Rent soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Total rent confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "rent_value_check": 0 + }, + { + "rent_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -7823,8 +8057,17 @@ }, "questions": { "rent_value_check": { - "check_answer_label": "Rent soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Total rent confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "rent_value_check": 0 + }, + { + "rent_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 7735e5ad9..0e737c43e 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -771,6 +771,38 @@ } ] }, + "void_date_value_check": { + "depends_on": [{ "voiddate_in_soft_range?": true }], + "title_text": { + "translation": "soft_validations.void_date.title_text" + }, + "informative_text": {}, + "questions": { + "void_date_value_check": { + "check_answer_label": "Void date confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "void_date_value_check": 0 + }, + { + "void_date_value_check": 1 + } + ] + }, + "header": "Are you sure the time between these dates is correct?", + "type": "interruption_screen", + "answer_options": { + "0": { + "value": "Yes" + }, + "1": { + "value": "No" + } + } + } + } + }, "new_build_handover_date": { "header": "", "description": "", @@ -867,6 +899,38 @@ "rsnvac": 19 } ] + }, + "property_major_repairs_value_check": { + "depends_on": [{ "major_repairs_date_in_soft_range?": true }], + "title_text": { + "translation": "soft_validations.major_repairs_date.title_text" + }, + "informative_text": {}, + "questions": { + "major_repairs_date_value_check": { + "check_answer_label": "Major repairs date confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "major_repairs_date_value_check": 0 + }, + { + "major_repairs_date_value_check": 1 + } + ] + }, + "header": "Are you sure the time between these dates is correct?", + "type": "interruption_screen", + "answer_options": { + "0": { + "value": "Yes" + }, + "1": { + "value": "No" + } + } + } + } } } }, @@ -1709,8 +1773,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -1753,8 +1826,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -2201,8 +2283,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -2247,8 +2338,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -2692,8 +2792,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -2738,8 +2847,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -3180,8 +3298,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -3226,8 +3353,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -3665,8 +3801,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -3711,8 +3856,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -4147,8 +4301,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -4193,8 +4356,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -4626,8 +4798,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -4672,8 +4853,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -5102,8 +5292,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person is retired?", "type": "interruption_screen", "answer_options": { @@ -5148,8 +5347,17 @@ }, "questions": { "retirement_value_check": { - "check_answer_label": "Retirement age soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Retirement confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "retirement_value_check": 0 + }, + { + "retirement_value_check": 1 + } + ] + }, "header": "Are you sure this person isn’t retired?", "type": "interruption_screen", "answer_options": { @@ -5369,8 +5577,16 @@ }, "questions": { "pregnancy_value_check": { - "check_answer_label": "Pregnancy soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Pregnancy confirmation", + "hidden_in_check_answers": { + "depends_on": [{ + "pregnancy_value_check": 0 + }, + { + "pregnancy_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -6818,8 +7034,17 @@ }, "questions": { "net_income_value_check": { - "check_answer_label": "Net income soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Net income confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "net_income_value_check": 0 + }, + { + "net_income_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -7719,8 +7944,17 @@ }, "questions": { "rent_value_check": { - "check_answer_label": "Rent soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Total rent confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "rent_value_check": 0 + }, + { + "rent_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { @@ -7763,8 +7997,17 @@ }, "questions": { "rent_value_check": { - "check_answer_label": "Rent soft validation", - "hidden_in_check_answers": true, + "check_answer_label": "Total rent confirmation", + "hidden_in_check_answers": { + "depends_on": [ + { + "rent_value_check": 0 + }, + { + "rent_value_check": 1 + } + ] + }, "header": "Are you sure this is correct?", "type": "interruption_screen", "answer_options": { diff --git a/config/locales/en.yml b/config/locales/en.yml index 56b70d397..107558f3c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -122,7 +122,7 @@ en: mrcdate: before_tenancy_start: "Enter a major repairs date that is before the tenancy start date" not_first_let: "Major repairs date must not be completed if the tenancy is a first let" - 730_days_before_tenancy_start: "Enter a major repairs completion date that is no more than 730 days before the tenancy start date" + ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date" void_date: ten_years_before_tenancy_start: "Enter a void date must no more than 10 years before the tenancy start date" before_tenancy_start: "Enter a void date must that is before the tenancy start date" @@ -319,6 +319,10 @@ en: 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." females_not_in_soft_age_range: "You also told us that any female tenants living at the property are in the following age ranges:
  • 11 to 16
  • 50 to 65
" + major_repairs_date: + title_text: "You told us the time between the start of the tenancy and the major repairs completion date is more than 2 years" + void_date: + title_text: "You told us the time between the start of the tenancy and the void date is more than 2 years" devise: two_factor_authentication: diff --git a/db/migrate/20220808090330_add_major_repairs_date_value_check.rb b/db/migrate/20220808090330_add_major_repairs_date_value_check.rb new file mode 100644 index 000000000..e1e666b65 --- /dev/null +++ b/db/migrate/20220808090330_add_major_repairs_date_value_check.rb @@ -0,0 +1,5 @@ +class AddMajorRepairsDateValueCheck < ActiveRecord::Migration[7.0] + def change + add_column :case_logs, :major_repairs_date_value_check, :integer + end +end diff --git a/db/migrate/20220808093035_add_void_date_value_check.rb b/db/migrate/20220808093035_add_void_date_value_check.rb new file mode 100644 index 000000000..c39a4d02b --- /dev/null +++ b/db/migrate/20220808093035_add_void_date_value_check.rb @@ -0,0 +1,5 @@ +class AddVoidDateValueCheck < ActiveRecord::Migration[7.0] + def change + add_column :case_logs, :void_date_value_check, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 1b65e5482..206fc69b0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -200,6 +200,8 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_10_152340) do t.integer "vacdays" t.bigint "scheme_id" t.bigint "location_id" + t.integer "major_repairs_date_value_check" + t.integer "void_date_value_check" t.index ["created_by_id"], name: "index_case_logs_on_created_by_id" t.index ["location_id"], name: "index_case_logs_on_location_id" t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id" diff --git a/spec/factories/case_log.rb b/spec/factories/case_log.rb index ec2d2c522..46c151fd2 100644 --- a/spec/factories/case_log.rb +++ b/spec/factories/case_log.rb @@ -110,6 +110,8 @@ FactoryBot.define do rp_dontknow { 0 } tenancyother { nil } net_income_value_check { nil } + void_date_value_check { 1 } + major_repairs_date_value_check { 1 } net_income_known { 0 } previous_la_known { 1 } property_owner_organisation { "Test" } diff --git a/spec/fixtures/files/case_logs_download.csv b/spec/fixtures/files/case_logs_download.csv index 5f5a1c96f..b1d71dfec 100644 --- a/spec/fixtures/files/case_logs_download.csv +++ b/spec/fixtures/files/case_logs_download.csv @@ -1,2 +1,2 @@ -id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,net_income_value_check,property_owner_organisation,property_manager_organisation,sale_or_letting,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,needstype,lettype,postcode_known,is_la_inferred,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,previous_la_known,is_previous_la_inferred,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,ethnic_other,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,rent_type,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,illness_type_0,retirement_value_check,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,scheme_id,location_id,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,,Supported housing,,,No,0,0,0,,0,,,,,,,,,,,,,,No,,,,,,,,,,,,,,,,,,,,0,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,9,,,{scheme_id},SE1 1TE,6,DLUHC,DLUHC,Danny Rojas +id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,net_income_value_check,property_owner_organisation,property_manager_organisation,sale_or_letting,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,needstype,lettype,postcode_known,is_la_inferred,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,previous_la_known,is_previous_la_inferred,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,ethnic_other,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,rent_type,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,illness_type_0,retirement_value_check,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,scheme_id,location_id,major_repairs_date_value_check,void_date_value_check,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,,Supported housing,,,No,0,0,0,,0,,,,,,,,,,,,,,No,,,,,,,,,,,,,,,,,,,,0,,,,,,,,0,,,,,,,,,,,,,,,,,,,,,,9,,,{scheme_id},SE1 1TE,,,6,DLUHC,DLUHC,Danny Rojas diff --git a/spec/fixtures/files/case_logs_download_non_support.csv b/spec/fixtures/files/case_logs_download_non_support.csv index 9bcd0e6aa..0c24699c0 100644 --- a/spec/fixtures/files/case_logs_download_non_support.csv +++ b/spec/fixtures/files/case_logs_download_non_support.csv @@ -1,2 +1,2 @@ -id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,ethnic_group,ethnic_other,has_benefits,renewal,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,illness_type_0,sheltered,scheme_id,location_id,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name -{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,0,,,,,,,,,,,,,,0,,0,,,,,,,,,,,,,,,,{scheme_id},SE1 1TE,6,DLUHC,DLUHC,Danny Rojas +id,status,created_at,updated_at,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,property_owner_organisation,property_manager_organisation,irproduct_other,purchaser_code,reason,propcode,majorrepairs,la,prevloc,hb,hbrentshortfall,property_relet,mrcdate,incref,sale_completion_date,startdate,armedforces,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,declaration,ppcodenk,ethnic_group,ethnic_other,has_benefits,renewal,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,illness_type_0,sheltered,scheme_id,location_id,major_repairs_date_value_check,void_date_value_check,unittype_sh,owning_organisation_name,managing_organisation_name,created_by_name +{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,SE1 1TE,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Westminster,,,,,,,,,,,,,,0,,,,,,,,,,,,,,0,,0,,,,,,,,,,,,,,,,{scheme_id},SE1 1TE,,,6,DLUHC,DLUHC,Danny Rojas diff --git a/spec/models/validations/date_validations_spec.rb b/spec/models/validations/date_validations_spec.rb index ca09c28c8..3370f9cc0 100644 --- a/spec/models/validations/date_validations_spec.rb +++ b/spec/models/validations/date_validations_spec.rb @@ -85,17 +85,17 @@ RSpec.describe Validations::DateValidations do expect(record.errors["mrcdate"]).to be_empty end - it "cannot be more than 2 years before the tenancy start date" do + it "cannot be more than 10 years before the tenancy start date" do record.startdate = Time.zone.local(2022, 2, 1) - record.mrcdate = Time.zone.local(2020, 1, 1) + record.mrcdate = Time.zone.local(2012, 1, 1) date_validator.validate_property_major_repairs(record) expect(record.errors["mrcdate"]) - .to include(match I18n.t("validations.property.mrcdate.730_days_before_tenancy_start")) + .to include(match I18n.t("validations.property.mrcdate.ten_years_before_tenancy_start")) end - it "must be within 2 years of the tenancy start date" do + it "must be within 10 years of the tenancy start date" do record.startdate = Time.zone.local(2022, 2, 1) - record.mrcdate = Time.zone.local(2020, 3, 1) + record.mrcdate = Time.zone.local(2012, 3, 1) date_validator.validate_property_major_repairs(record) expect(record.errors["mrcdate"]).to be_empty end diff --git a/spec/models/validations/soft_validations_spec.rb b/spec/models/validations/soft_validations_spec.rb index b28a53b9e..64d7ba2ab 100644 --- a/spec/models/validations/soft_validations_spec.rb +++ b/spec/models/validations/soft_validations_spec.rb @@ -205,4 +205,36 @@ RSpec.describe Validations::SoftValidations do end end end + + describe "major repairs date soft validations" do + context "when the major repairs date is within 10 years of the tenancy start date" do + it "shows the interruption screen" do + record.update!(startdate: Time.zone.local(2022, 2, 1), mrcdate: Time.zone.local(2013, 2, 1)) + expect(record.major_repairs_date_in_soft_range?).to be true + end + end + + context "when the major repairs date is less than 2 years before the tenancy start date" do + it "does not show the interruption screen" do + record.update!(startdate: Time.zone.local(2022, 2, 1), mrcdate: Time.zone.local(2021, 2, 1)) + expect(record.major_repairs_date_in_soft_range?).to be false + end + end + end + + describe "void date soft validations" do + context "when the void date is within 10 years of the tenancy start date" do + it "shows the interruption screen" do + record.update!(startdate: Time.zone.local(2022, 2, 1), voiddate: Time.zone.local(2013, 2, 1)) + expect(record.voiddate_in_soft_range?).to be true + end + end + + context "when the void date is less than 2 years before the tenancy start date" do + it "does not show the interruption screen" do + record.update!(startdate: Time.zone.local(2022, 2, 1), voiddate: Time.zone.local(2021, 2, 1)) + expect(record.voiddate_in_soft_range?).to be false + end + end + end end From e82f8ed88ec8ee9d6fe863ea9823ff816252f6c4 Mon Sep 17 00:00:00 2001 From: Ted-U <92022120+Ted-U@users.noreply.github.com> Date: Mon, 22 Aug 2022 11:02:26 +0100 Subject: [PATCH 29/29] CLDC-1310-seperate-tenants-fix (#833) * fix pushing to wrong branch * Make household characteristics check answers match design * update test form * add feature specs * lint fixes * make changes to forms * update spec description * update check for summary card in check answers * add specs * fix specs * extract helper method * lint fixes Co-authored-by: Dushan Despotovic --- ...swers_summary_list_card_component.html.erb | 37 +++++++++ ...eck_answers_summary_list_card_component.rb | 18 +++++ app/helpers/check_answers_helper.rb | 4 + app/models/form/question.rb | 3 +- app/views/form/check_answers.html.erb | 14 +++- config/forms/2021_2022.json | 55 +++++++++++++ config/forms/2022_2023.json | 77 ++++++++++++++++++- ...nswers_summary_list_card_component_spec.rb | 27 +++++++ spec/features/form/check_answers_page_spec.rb | 13 ++++ spec/fixtures/forms/2021_2022.json | 33 ++++++++ spec/models/form/subsection_spec.rb | 4 +- spec/models/form_handler_spec.rb | 2 +- spec/models/form_spec.rb | 2 +- 13 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 app/components/check_answers_summary_list_card_component.html.erb create mode 100644 app/components/check_answers_summary_list_card_component.rb create mode 100644 spec/components/check_answers_summary_list_card_component_spec.rb diff --git a/app/components/check_answers_summary_list_card_component.html.erb b/app/components/check_answers_summary_list_card_component.html.erb new file mode 100644 index 000000000..c2e3c8ee5 --- /dev/null +++ b/app/components/check_answers_summary_list_card_component.html.erb @@ -0,0 +1,37 @@ +
+ <% if applicable_questions.first.check_answers_card_number != 0 %> +
+ <% if applicable_questions.first.check_answers_card_number == 1 %> +

Lead tenant

+ <% end %> + <% if applicable_questions.first.check_answers_card_number > 1 %> +

Person <%= applicable_questions.first.check_answers_card_number %>

+ <% end %> +
+ <% end %> +
+ <%= govuk_summary_list do |summary_list| %> + <% applicable_questions.each do |question| %> + <% summary_list.row do |row| %> + <% row.key { question.check_answer_label.to_s.presence || question.header.to_s } %> + <% row.value do %> + <%= get_answer_label(question) %> + <% extra_value = question.get_extra_check_answer_value(case_log) %> + <% if extra_value %> + <%= extra_value %> + <% end %> +
+ <% question.get_inferred_answers(case_log).each do |inferred_answer| %> + <%= inferred_answer %> + <% end %> + <% end %> + <% row.action( + text: question.action_text(case_log), + href: question.action_href(case_log, question.page.id), + visually_hidden_text: question.check_answer_label.to_s.downcase, + ) %> + <% end %> + <% end %> + <% end %> +
+
diff --git a/app/components/check_answers_summary_list_card_component.rb b/app/components/check_answers_summary_list_card_component.rb new file mode 100644 index 000000000..0ae6afcac --- /dev/null +++ b/app/components/check_answers_summary_list_card_component.rb @@ -0,0 +1,18 @@ +class CheckAnswersSummaryListCardComponent < ViewComponent::Base + attr_reader :questions, :case_log, :user + + def initialize(questions:, case_log:, user:) + @questions = questions + @case_log = case_log + @user = user + super + end + + def applicable_questions + questions.reject { |q| q.hidden_in_check_answers?(case_log, user) } + end + + def get_answer_label(question) + question.answer_label(case_log).presence || "You didn’t answer this question".html_safe + end +end diff --git a/app/helpers/check_answers_helper.rb b/app/helpers/check_answers_helper.rb index 760c08ab0..9c0401f79 100644 --- a/app/helpers/check_answers_helper.rb +++ b/app/helpers/check_answers_helper.rb @@ -24,6 +24,10 @@ module CheckAnswersHelper end end + def any_questions_have_summary_card_number?(subsection, case_log) + subsection.applicable_questions(case_log).map(&:check_answers_card_number).compact.length.positive? + end + private def answered_questions_count(subsection, case_log, current_user) diff --git a/app/models/form/question.rb b/app/models/form/question.rb index c21477bc9..11fa40c0b 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -3,7 +3,7 @@ class Form::Question :type, :min, :max, :step, :width, :fields_to_add, :result_field, :conditional_for, :readonly, :answer_options, :page, :check_answer_label, :inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value, - :guidance_partial, :prefix, :suffix, :requires_js, :fields_added, :derived + :guidance_partial, :prefix, :suffix, :requires_js, :fields_added, :derived, :check_answers_card_number module GuidancePosition TOP = 1 @@ -37,6 +37,7 @@ class Form::Question @suffix = hsh["suffix"] @requires_js = hsh["requires_js"] @fields_added = hsh["fields_added"] + @check_answers_card_number = hsh["check_answers_card_number"] end end diff --git a/app/views/form/check_answers.html.erb b/app/views/form/check_answers.html.erb index 8db15d81e..44b4f89d4 100644 --- a/app/views/form/check_answers.html.erb +++ b/app/views/form/check_answers.html.erb @@ -17,10 +17,16 @@ <% end %> <%= display_answered_questions_summary(subsection, @case_log, current_user) %> - <%= render partial: "form/check_answers_summary_list", locals: { - subsection:, - case_log: @case_log, - } %> + <% if any_questions_have_summary_card_number?(subsection, @case_log) %> + <% subsection.applicable_questions(@case_log).group_by(&:check_answers_card_number).values.each do |question_group| %> + <%= render CheckAnswersSummaryListCardComponent.new(questions: question_group, case_log: @case_log, user: current_user) %> + <% end %> + <% else %> + <%= render partial: "form/check_answers_summary_list", locals: { + subsection:, + case_log: @case_log, + } %> + <% end %> <%= form_with model: @case_log, method: "get" do |f| %> <%= f.govuk_submit "Save and return to log" do %> diff --git a/config/forms/2021_2022.json b/config/forms/2021_2022.json index d365a5a8f..e2e0f0131 100644 --- a/config/forms/2021_2022.json +++ b/config/forms/2021_2022.json @@ -1127,6 +1127,7 @@ "header": "", "guidance_partial": "privacy_notice", "check_answer_label": "Tenant has seen the privacy notice", + "check_answers_card_number": 0, "type": "checkbox", "answer_options": { "declaration": { @@ -1141,6 +1142,7 @@ "description": "", "questions": { "hhmemb": { + "check_answers_card_number": 0, "check_answer_label": "Number of household members", "header": "How many people live in the household for this letting?", "hint_text": "You can provide details for a maximum of 8 people.", @@ -1240,6 +1242,7 @@ "description": "", "questions": { "age1_known": { + "check_answers_card_number": 1, "header": "Do you know the lead tenant’s age?", "hint_text": "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1266,6 +1269,7 @@ } }, "age1": { + "check_answers_card_number": 1, "header": "Age", "check_answer_label": "Lead tenant’s age", "type": "numeric", @@ -1371,6 +1375,7 @@ "questions": { "sex1": { "check_answer_label": "Lead tenant’s gender identity", + "check_answers_card_number": 1, "header": "Which of these best describes the lead tenant’s gender identity?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1483,6 +1488,7 @@ "questions": { "ethnic_group": { "check_answer_label": "Lead tenant’s ethnic group", + "check_answers_card_number": 1, "header": "What is the lead tenant’s ethnic group?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1518,6 +1524,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Arab background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1539,6 +1546,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Asian or Asian British background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1569,6 +1577,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Black, African, Caribbean or Black British background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1593,6 +1602,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Mixed or Multiple ethnic groups background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1620,6 +1630,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s White background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1647,6 +1658,7 @@ "description": "", "questions": { "national": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s nationality", "header": "What is the lead tenant’s nationality?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1716,6 +1728,7 @@ "ecstat1": { "check_answer_label": "Lead tenant’s working situation", "header": "Which of these best describes the lead tenant’s working situation?", + "check_answers_card_number": 1, "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", "answer_options": { @@ -1871,6 +1884,7 @@ "questions": { "details_known_2": { "check_answer_label": "Details known for person 2", + "check_answers_card_number": 2, "header": "Do you know details for person 2?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -1914,6 +1928,7 @@ "questions": { "relat2": { "check_answer_label": "Person 2’s relationship to the lead tenant", + "check_answers_card_number": 2, "header": "What is person 2’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -1949,6 +1964,7 @@ "questions": { "age2_known": { "header": "Do you know person 2’s age?", + "check_answers_card_number": 2, "hint_text": "", "type": "radio", "answer_options": { @@ -1976,6 +1992,7 @@ "age2": { "header": "Age", "check_answer_label": "Person 2’s age", + "check_answers_card_number": 2, "type": "numeric", "min": 0, "max": 120, @@ -2087,6 +2104,7 @@ "sex2": { "check_answer_label": "Person 2’s gender identity", "header": "Which of these best describes person 2’s gender identity?", + "check_answers_card_number": 2, "hint_text": "", "type": "radio", "answer_options": { @@ -2208,6 +2226,7 @@ "questions": { "ecstat2": { "check_answer_label": "Person 2’s working situation", + "check_answers_card_number": 2, "header": "Which of these best describes person 2’s working situation?", "hint_text": "", "type": "radio", @@ -2383,6 +2402,7 @@ "questions": { "details_known_3": { "check_answer_label": "Details known for person 3", + "check_answers_card_number": 3, "header": "Do you know details for person 3?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -2423,6 +2443,7 @@ "questions": { "relat3": { "check_answer_label": "Person 3’s relationship to the lead tenant", + "check_answers_card_number": 3, "header": "What is person 3’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -2458,6 +2479,7 @@ "questions": { "age3_known": { "header": "Do you know person 3’s age?", + "check_answers_card_number": 3, "hint_text": "", "type": "radio", "answer_options": { @@ -2485,6 +2507,7 @@ "age3": { "header": "Age", "check_answer_label": "Person 3’s age", + "check_answers_card_number": 3, "type": "numeric", "min": 0, "max": 120, @@ -2595,6 +2618,7 @@ "questions": { "sex3": { "check_answer_label": "Person 3’s gender identity", + "check_answers_card_number": 3, "header": "Which of these best describes person 3’s gender identity?", "hint_text": "", "type": "radio", @@ -2717,6 +2741,7 @@ "questions": { "ecstat3": { "check_answer_label": "Person 3’s working situation", + "check_answers_card_number": 3, "header": "Which of these best describes person 3’s working situation?", "hint_text": "", "type": "radio", @@ -2892,6 +2917,7 @@ "questions": { "details_known_4": { "check_answer_label": "Details known for person 4", + "check_answers_card_number": 4, "header": "Do you know details for person 4?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -2929,6 +2955,7 @@ "questions": { "relat4": { "check_answer_label": "Person 4’s relationship to the lead tenant", + "check_answers_card_number": 4, "header": "What is person 4’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -2964,6 +2991,7 @@ "questions": { "age4_known": { "header": "Do you know person 4’s age?", + "check_answers_card_number": 4, "hint_text": "", "type": "radio", "answer_options": { @@ -2991,6 +3019,7 @@ "age4": { "header": "Age", "check_answer_label": "Person 4’s age", + "check_answers_card_number": 4, "type": "numeric", "min": 0, "max": 120, @@ -3101,6 +3130,7 @@ "questions": { "sex4": { "check_answer_label": "Person 4’s gender identity", + "check_answers_card_number": 4, "header": "Which of these best describes person 4’s gender identity?", "hint_text": "", "type": "radio", @@ -3223,6 +3253,7 @@ "questions": { "ecstat4": { "check_answer_label": "Person 4’s working situation", + "check_answers_card_number": 4, "header": "Which of these best describes person 4’s working situation?", "hint_text": "", "type": "radio", @@ -3398,6 +3429,7 @@ "questions": { "details_known_5": { "check_answer_label": "Details known for person 5", + "check_answers_card_number": 5, "header": "Do you know details for person 5?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -3432,6 +3464,7 @@ "questions": { "relat5": { "check_answer_label": "Person 5’s relationship to the lead tenant", + "check_answers_card_number": 5, "header": "What is person 5’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -3467,6 +3500,7 @@ "questions": { "age5_known": { "header": "Do you know person 5’s age?", + "check_answers_card_number": 5, "hint_text": "", "type": "radio", "answer_options": { @@ -3494,6 +3528,7 @@ "age5": { "header": "Age", "check_answer_label": "Person 5’s age", + "check_answers_card_number": 5, "type": "numeric", "min": 0, "max": 120, @@ -3604,6 +3639,7 @@ "questions": { "sex5": { "check_answer_label": "Person 5’s gender identity", + "check_answers_card_number": 5, "header": "Which of these best describes person 5’s gender identity?", "hint_text": "", "type": "radio", @@ -3726,6 +3762,7 @@ "questions": { "ecstat5": { "check_answer_label": "Person 5’s working situation", + "check_answers_card_number": 5, "header": "Which of these best describes person 5’s working situation?", "hint_text": "", "type": "radio", @@ -3901,6 +3938,7 @@ "questions": { "details_known_6": { "check_answer_label": "Details known for person 6", + "check_answers_card_number": 6, "header": "Do you know details for person 6?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -3932,6 +3970,7 @@ "questions": { "relat6": { "check_answer_label": "Person 6’s relationship to the lead tenant", + "check_answers_card_number": 6, "header": "What is person 6’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -3967,6 +4006,7 @@ "questions": { "age6_known": { "header": "Do you know person 6’s age?", + "check_answers_card_number": 6, "hint_text": "", "type": "radio", "answer_options": { @@ -3994,6 +4034,7 @@ "age6": { "header": "Age", "check_answer_label": "Person 6’s age", + "check_answers_card_number": 6, "type": "numeric", "min": 0, "max": 120, @@ -4104,6 +4145,7 @@ "questions": { "sex6": { "check_answer_label": "Person 6’s gender identity", + "check_answers_card_number": 6, "header": "Which of these best describes person 6’s gender identity?", "hint_text": "", "type": "radio", @@ -4226,6 +4268,7 @@ "questions": { "ecstat6": { "check_answer_label": "Person 6’s working situation", + "check_answers_card_number": 6, "header": "Which of these best describes person 6’s working situation?", "hint_text": "", "type": "radio", @@ -4401,6 +4444,7 @@ "questions": { "details_known_7": { "check_answer_label": "Details known for person 7", + "check_answers_card_number": 7, "header": "Do you know details for person 7?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -4429,6 +4473,7 @@ "questions": { "relat7": { "check_answer_label": "Person 7’s relationship to the lead tenant", + "check_answers_card_number": 7, "header": "What is person 7’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -4464,6 +4509,7 @@ "questions": { "age7_known": { "header": "Do you know person 7’s age?", + "check_answers_card_number": 7, "hint_text": "", "type": "radio", "answer_options": { @@ -4491,6 +4537,7 @@ "age7": { "header": "Age", "check_answer_label": "Person 7’s age", + "check_answers_card_number": 7, "type": "numeric", "min": 0, "max": 120, @@ -4601,6 +4648,7 @@ "questions": { "sex7": { "check_answer_label": "Person 7’s gender identity", + "check_answers_card_number": 7, "header": "Which of these best describes person 7’s gender identity?", "hint_text": "", "type": "radio", @@ -4723,6 +4771,7 @@ "questions": { "ecstat7": { "check_answer_label": "Person 7’s working situation", + "check_answers_card_number": 7, "header": "Which of these best describes person 7’s working situation?", "hint_text": "", "type": "radio", @@ -4898,6 +4947,7 @@ "questions": { "details_known_8": { "check_answer_label": "Details known for person 8", + "check_answers_card_number": 8, "header": "Do you know details for person 8?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -4923,6 +4973,7 @@ "questions": { "relat8": { "check_answer_label": "Person 8’s relationship to the lead tenant", + "check_answers_card_number": 8, "header": "What is person 8’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -4958,6 +5009,7 @@ "questions": { "age8_known": { "header": "Do you know person 8’s age?", + "check_answers_card_number": 8, "hint_text": "", "type": "radio", "answer_options": { @@ -4985,6 +5037,7 @@ "age8": { "header": "Age", "check_answer_label": "Person 8’s age", + "check_answers_card_number": 8, "type": "numeric", "min": 0, "max": 120, @@ -5095,6 +5148,7 @@ "questions": { "sex8": { "check_answer_label": "Person 8’s gender identity", + "check_answers_card_number": 8, "header": "Which of these best describes person 8’s gender identity?", "hint_text": "", "type": "radio", @@ -5217,6 +5271,7 @@ "questions": { "ecstat8": { "check_answer_label": "Person 8’s working situation", + "check_answers_card_number": 8, "header": "Which of these best describes person 8’s working situation?", "hint_text": "", "type": "radio", diff --git a/config/forms/2022_2023.json b/config/forms/2022_2023.json index 0e737c43e..0102d5587 100644 --- a/config/forms/2022_2023.json +++ b/config/forms/2022_2023.json @@ -1162,6 +1162,7 @@ "header": "", "guidance_partial": "privacy_notice", "check_answer_label": "Tenant has seen the privacy notice", + "check_answers_card_number": 0, "type": "checkbox", "answer_options": { "declaration": { @@ -1176,6 +1177,7 @@ "description": "", "questions": { "hhmemb": { + "check_answers_card_number": 0, "check_answer_label": "Number of household members", "header": "How many people live in the household for this letting?", "hint_text": "You can provide details for a maximum of 8 people.", @@ -1228,7 +1230,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ @@ -1271,6 +1277,7 @@ "description": "", "questions": { "age1_known": { + "check_answers_card_number": 1, "header": "Do you know the lead tenant’s age?", "hint_text": "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1297,6 +1304,7 @@ } }, "age1": { + "check_answers_card_number": 1, "header": "Age", "check_answer_label": "Lead tenant’s age", "type": "numeric", @@ -1354,7 +1362,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_age_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ @@ -1398,6 +1410,7 @@ "questions": { "sex1": { "check_answer_label": "Lead tenant’s gender identity", + "check_answers_card_number": 1, "header": "Which of these best describes the lead tenant’s gender identity?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1462,7 +1475,11 @@ } }, "females_in_soft_age_range_in_pregnant_household_lead_value_check": { - "depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }], + "depends_on": [ + { + "female_in_pregnant_household_in_soft_validation_range?": true + } + ], "title_text": { "translation": "soft_validations.pregnancy.title", "arguments": [ @@ -1506,6 +1523,7 @@ "questions": { "ethnic_group": { "check_answer_label": "Lead tenant’s ethnic group", + "check_answers_card_number": 1, "header": "What is the lead tenant’s ethnic group?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", @@ -1541,6 +1559,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Arab background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1562,6 +1581,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Asian or Asian British background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1592,6 +1612,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Black, African, Caribbean or Black British background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1616,6 +1637,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s Mixed or Multiple ethnic groups background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1643,6 +1665,7 @@ "description": "", "questions": { "ethnic": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s ethnic background", "header": "Which of the following best describes the lead tenant’s White background?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1670,6 +1693,7 @@ "description": "", "questions": { "national": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s nationality", "header": "What is the lead tenant’s nationality?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -1703,6 +1727,7 @@ "ecstat1": { "check_answer_label": "Lead tenant’s working situation", "header": "Which of these best describes the lead tenant’s working situation?", + "check_answers_card_number": 1, "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", "type": "radio", "answer_options": { @@ -1798,7 +1823,9 @@ } }, "lead_tenant_over_retirement_value_check": { - "depends_on": [{ "person_1_not_retired_over_soft_max_age?": true }], + "depends_on": [ + { "person_1_not_retired_over_soft_max_age?": true } + ], "title_text": { "translation": "soft_validations.retirement.max.title", "arguments": [ @@ -1856,6 +1883,7 @@ "questions": { "details_known_2": { "check_answer_label": "Details known for person 2", + "check_answers_card_number": 2, "header": "Do you know details for person 2?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -1899,6 +1927,7 @@ "questions": { "relat2": { "check_answer_label": "Person 2’s relationship to the lead tenant", + "check_answers_card_number": 2, "header": "What is person 2’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -1934,6 +1963,7 @@ "questions": { "age2_known": { "header": "Do you know person 2’s age?", + "check_answers_card_number": 2, "hint_text": "", "type": "radio", "answer_options": { @@ -1961,6 +1991,7 @@ "age2": { "header": "Age", "check_answer_label": "Person 2’s age", + "check_answers_card_number": 2, "type": "numeric", "min": 0, "max": 120, @@ -2072,6 +2103,7 @@ "sex2": { "check_answer_label": "Person 2’s gender identity", "header": "Which of these best describes person 2’s gender identity?", + "check_answers_card_number": 2, "hint_text": "", "type": "radio", "answer_options": { @@ -2193,6 +2225,7 @@ "questions": { "ecstat2": { "check_answer_label": "Person 2’s working situation", + "check_answers_card_number": 2, "header": "Which of these best describes person 2’s working situation?", "hint_text": "", "type": "radio", @@ -2368,6 +2401,7 @@ "questions": { "details_known_3": { "check_answer_label": "Details known for person 3", + "check_answers_card_number": 3, "header": "Do you know details for person 3?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -2408,6 +2442,7 @@ "questions": { "relat3": { "check_answer_label": "Person 3’s relationship to the lead tenant", + "check_answers_card_number": 3, "header": "What is person 3’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -2443,6 +2478,7 @@ "questions": { "age3_known": { "header": "Do you know person 3’s age?", + "check_answers_card_number": 3, "hint_text": "", "type": "radio", "answer_options": { @@ -2470,6 +2506,7 @@ "age3": { "header": "Age", "check_answer_label": "Person 3’s age", + "check_answers_card_number": 3, "type": "numeric", "min": 0, "max": 120, @@ -2580,6 +2617,7 @@ "questions": { "sex3": { "check_answer_label": "Person 3’s gender identity", + "check_answers_card_number": 3, "header": "Which of these best describes person 3’s gender identity?", "hint_text": "", "type": "radio", @@ -2702,6 +2740,7 @@ "questions": { "ecstat3": { "check_answer_label": "Person 3’s working situation", + "check_answers_card_number": 3, "header": "Which of these best describes person 3’s working situation?", "hint_text": "", "type": "radio", @@ -2877,6 +2916,7 @@ "questions": { "details_known_4": { "check_answer_label": "Details known for person 4", + "check_answers_card_number": 4, "header": "Do you know details for person 4?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -2914,6 +2954,7 @@ "questions": { "relat4": { "check_answer_label": "Person 4’s relationship to the lead tenant", + "check_answers_card_number": 4, "header": "What is person 4’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -2949,6 +2990,7 @@ "questions": { "age4_known": { "header": "Do you know person 4’s age?", + "check_answers_card_number": 4, "hint_text": "", "type": "radio", "answer_options": { @@ -2976,6 +3018,7 @@ "age4": { "header": "Age", "check_answer_label": "Person 4’s age", + "check_answers_card_number": 4, "type": "numeric", "min": 0, "max": 120, @@ -3086,6 +3129,7 @@ "questions": { "sex4": { "check_answer_label": "Person 4’s gender identity", + "check_answers_card_number": 4, "header": "Which of these best describes person 4’s gender identity?", "hint_text": "", "type": "radio", @@ -3208,6 +3252,7 @@ "questions": { "ecstat4": { "check_answer_label": "Person 4’s working situation", + "check_answers_card_number": 4, "header": "Which of these best describes person 4’s working situation?", "hint_text": "", "type": "radio", @@ -3383,6 +3428,7 @@ "questions": { "details_known_5": { "check_answer_label": "Details known for person 5", + "check_answers_card_number": 5, "header": "Do you know details for person 5?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -3417,6 +3463,7 @@ "questions": { "relat5": { "check_answer_label": "Person 5’s relationship to the lead tenant", + "check_answers_card_number": 5, "header": "What is person 5’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -3452,6 +3499,7 @@ "questions": { "age5_known": { "header": "Do you know person 5’s age?", + "check_answers_card_number": 5, "hint_text": "", "type": "radio", "answer_options": { @@ -3479,6 +3527,7 @@ "age5": { "header": "Age", "check_answer_label": "Person 5’s age", + "check_answers_card_number": 5, "type": "numeric", "min": 0, "max": 120, @@ -3589,6 +3638,7 @@ "questions": { "sex5": { "check_answer_label": "Person 5’s gender identity", + "check_answers_card_number": 5, "header": "Which of these best describes person 5’s gender identity?", "hint_text": "", "type": "radio", @@ -3711,6 +3761,7 @@ "questions": { "ecstat5": { "check_answer_label": "Person 5’s working situation", + "check_answers_card_number": 5, "header": "Which of these best describes person 5’s working situation?", "hint_text": "", "type": "radio", @@ -3886,6 +3937,7 @@ "questions": { "details_known_6": { "check_answer_label": "Details known for person 6", + "check_answers_card_number": 6, "header": "Do you know details for person 6?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -3917,6 +3969,7 @@ "questions": { "relat6": { "check_answer_label": "Person 6’s relationship to the lead tenant", + "check_answers_card_number": 6, "header": "What is person 6’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -3952,6 +4005,7 @@ "questions": { "age6_known": { "header": "Do you know person 6’s age?", + "check_answers_card_number": 6, "hint_text": "", "type": "radio", "answer_options": { @@ -3979,6 +4033,7 @@ "age6": { "header": "Age", "check_answer_label": "Person 6’s age", + "check_answers_card_number": 6, "type": "numeric", "min": 0, "max": 120, @@ -4089,6 +4144,7 @@ "questions": { "sex6": { "check_answer_label": "Person 6’s gender identity", + "check_answers_card_number": 6, "header": "Which of these best describes person 6’s gender identity?", "hint_text": "", "type": "radio", @@ -4211,6 +4267,7 @@ "questions": { "ecstat6": { "check_answer_label": "Person 6’s working situation", + "check_answers_card_number": 6, "header": "Which of these best describes person 6’s working situation?", "hint_text": "", "type": "radio", @@ -4386,6 +4443,7 @@ "questions": { "details_known_7": { "check_answer_label": "Details known for person 7", + "check_answers_card_number": 7, "header": "Do you know details for person 7?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -4414,6 +4472,7 @@ "questions": { "relat7": { "check_answer_label": "Person 7’s relationship to the lead tenant", + "check_answers_card_number": 7, "header": "What is person 7’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -4449,6 +4508,7 @@ "questions": { "age7_known": { "header": "Do you know person 7’s age?", + "check_answers_card_number": 7, "hint_text": "", "type": "radio", "answer_options": { @@ -4476,6 +4536,7 @@ "age7": { "header": "Age", "check_answer_label": "Person 7’s age", + "check_answers_card_number": 7, "type": "numeric", "min": 0, "max": 120, @@ -4586,6 +4647,7 @@ "questions": { "sex7": { "check_answer_label": "Person 7’s gender identity", + "check_answers_card_number": 7, "header": "Which of these best describes person 7’s gender identity?", "hint_text": "", "type": "radio", @@ -4708,6 +4770,7 @@ "questions": { "ecstat7": { "check_answer_label": "Person 7’s working situation", + "check_answers_card_number": 7, "header": "Which of these best describes person 7’s working situation?", "hint_text": "", "type": "radio", @@ -4883,6 +4946,7 @@ "questions": { "details_known_8": { "check_answer_label": "Details known for person 8", + "check_answers_card_number": 8, "header": "Do you know details for person 8?", "hint_text": "You must provide details for everyone in the household if you know them.", "type": "radio", @@ -4908,6 +4972,7 @@ "questions": { "relat8": { "check_answer_label": "Person 8’s relationship to the lead tenant", + "check_answers_card_number": 8, "header": "What is person 8’s relationship to the lead tenant?", "hint_text": "", "type": "radio", @@ -4943,6 +5008,7 @@ "questions": { "age8_known": { "header": "Do you know person 8’s age?", + "check_answers_card_number": 8, "hint_text": "", "type": "radio", "answer_options": { @@ -4970,6 +5036,7 @@ "age8": { "header": "Age", "check_answer_label": "Person 8’s age", + "check_answers_card_number": 8, "type": "numeric", "min": 0, "max": 120, @@ -5080,6 +5147,7 @@ "questions": { "sex8": { "check_answer_label": "Person 8’s gender identity", + "check_answers_card_number": 8, "header": "Which of these best describes person 8’s gender identity?", "hint_text": "", "type": "radio", @@ -5202,6 +5270,7 @@ "questions": { "ecstat8": { "check_answer_label": "Person 8’s working situation", + "check_answers_card_number": 8, "header": "Which of these best describes person 8’s working situation?", "hint_text": "", "type": "radio", diff --git a/spec/components/check_answers_summary_list_card_component_spec.rb b/spec/components/check_answers_summary_list_card_component_spec.rb new file mode 100644 index 000000000..9ccfebe51 --- /dev/null +++ b/spec/components/check_answers_summary_list_card_component_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do + context "when given a set of questions" do + let(:user) { FactoryBot.build(:user) } + let(:case_log) { FactoryBot.build(:case_log, :completed, age2: 99) } + let(:subsection_id) { "household_characteristics" } + let(:subsection) { case_log.form.get_subsection(subsection_id) } + let(:questions) { subsection.applicable_questions(case_log) } + + it "renders a summary list card for the answers to those questions" do + result = render_inline(described_class.new(questions:, case_log:, user:)) + expect(result).to have_content(questions.first.answer_label(case_log)) + end + + it "applicable questions doesn't return questions that are hidden in check answers" do + summary_list = described_class.new(questions:, case_log:, user:) + expect(summary_list.applicable_questions.map(&:id).include?("retirement_value_check")).to eq(false) + end + + it "has the correct answer label for a question" do + summary_list = described_class.new(questions:, case_log:, user:) + sex1_question = questions[2] + expect(summary_list.get_answer_label(sex1_question)).to eq("Female") + end + end +end diff --git a/spec/features/form/check_answers_page_spec.rb b/spec/features/form/check_answers_page_spec.rb index 4e4253470..3ba4e28ab 100644 --- a/spec/features/form/check_answers_page_spec.rb +++ b/spec/features/form/check_answers_page_spec.rb @@ -133,6 +133,19 @@ RSpec.describe "Form Check Answers Page" do end end + it "does not group questions into summary cards if the questions in the subsection don't have a check_answers_card_number attribute" do + visit("/logs/#{completed_case_log.id}/household-needs/check-answers") + assert_selector ".x-govuk-summary-card__title", count: 0 + end + + context "when the user is checking their answers for the household characteristics subsection" do + it "they see a seperate summary card for each member of the household" do + visit("/logs/#{completed_case_log.id}/#{subsection}/check-answers") + assert_selector ".x-govuk-summary-card__title", text: "Lead tenant", count: 1 + assert_selector ".x-govuk-summary-card__title", text: "Person 2", count: 1 + end + end + context "when viewing setup section answers" do before do FactoryBot.create(:location, scheme:) diff --git a/spec/fixtures/forms/2021_2022.json b/spec/fixtures/forms/2021_2022.json index 9a24381e9..ea8b3ce40 100644 --- a/spec/fixtures/forms/2021_2022.json +++ b/spec/fixtures/forms/2021_2022.json @@ -13,6 +13,7 @@ "tenant_code_test": { "questions": { "tenancycode": { + "check_answers_card_number": 0, "check_answer_label": "Tenant code", "header": "What is the tenant code?", "hint_text": "This is how you usually refer to this tenancy on your own systems.", @@ -31,6 +32,7 @@ "person_1_age": { "questions": { "age1": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s age", "header": "What is the tenant’s age?", "type": "numeric", @@ -52,6 +54,7 @@ "person_1_gender": { "questions": { "sex1": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s gender identity", "header": "Which of these best describes the tenant’s gender identity?", "type": "radio", @@ -77,6 +80,7 @@ "description": "", "questions": { "ecstat1": { + "check_answers_card_number": 1, "check_answer_label": "Lead tenant’s working situation", "header": "Which of these best describes the lead tenant’s socks?", "hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.", @@ -125,6 +129,7 @@ "household_number_of_members": { "questions": { "hhmemb": { + "check_answers_card_number": 0, "check_answer_label": "Number of Household Members", "header": "How many people are there in the household?", "hint_text": "The maximum number of members is 8", @@ -140,6 +145,7 @@ } }, "relat2": { + "check_answers_card_number": 2, "check_answer_label": "Person 2’s relationship to lead tenant", "header": "What is person 2’s relationship to lead tenant", "type": "radio", @@ -153,6 +159,7 @@ } }, "age2": { + "check_answers_card_number": 2, "check_answer_label": "Person 2’s age", "header": "Do you know person 2’s age?", "type": "numeric", @@ -162,6 +169,7 @@ "width": 2 }, "sex2": { + "check_answers_card_number": 2, "check_answer_label": "Person 2’s gender identity", "header": "Which of these best describes person 2’s gender identity?", "type": "radio", @@ -182,11 +190,35 @@ } } }, + "retirement_value_check": { + "questions": { + "retirement_value_check": { + "check_answer_label": "Retirement age soft validation", + "hidden_in_check_answers": true, + "header": "Are you sure this person is retired?", + "type": "radio", + "answer_options": { + "0": { + "value": "Yes" + }, + "1": { + "value": "No" + } + } + } + }, + "depends_on": [ + { + "age2": { "operator": ">", "operand": 50 } + } + ] + }, "person_2_working_situation": { "header": "", "description": "", "questions": { "ecstat2": { + "check_answers_card_number": 2, "check_answer_label": "Person 2’s Work", "header": "Which of these best describes person 2’s working situation?", "type": "radio", @@ -216,6 +248,7 @@ "propcode": { "questions": { "propcode": { + "check_answers_card_number": 0, "check_answer_label": "", "header": "property reference?", "type": "text" diff --git a/spec/models/form/subsection_spec.rb b/spec/models/form/subsection_spec.rb index 1b876520a..95208b8e5 100644 --- a/spec/models/form/subsection_spec.rb +++ b/spec/models/form/subsection_spec.rb @@ -25,12 +25,12 @@ RSpec.describe Form::Subsection, type: :model do end it "has pages" do - expected_pages = %w[tenant_code_test person_1_age person_1_gender person_1_working_situation household_number_of_members person_2_working_situation propcode] + expected_pages = %w[tenant_code_test person_1_age person_1_gender person_1_working_situation household_number_of_members retirement_value_check person_2_working_situation propcode] expect(subsection.pages.map(&:id)).to eq(expected_pages) end it "has questions" do - expected_questions = %w[tenancycode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 ecstat2 propcode] + expected_questions = %w[tenancycode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 propcode] expect(subsection.questions.map(&:id)).to eq(expected_questions) end diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 2222d737e..94a3af2fe 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -17,7 +17,7 @@ RSpec.describe FormHandler do form_handler = described_class.instance form = form_handler.get_form(test_form_name) expect(form).to be_a(Form) - expect(form.pages.count).to eq(44) + expect(form.pages.count).to eq(45) end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index 291343cd9..4b6b66672 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -178,7 +178,7 @@ RSpec.describe Form, type: :model do describe "invalidated_page_questions" do let(:case_log) { FactoryBot.create(:case_log, :in_progress, needstype: 1) } - let(:expected_invalid) { %w[scheme_id condition_effects cbl conditional_question_no_second_question net_income_value_check dependent_question offered layear declaration] } + let(:expected_invalid) { %w[scheme_id retirement_value_check condition_effects cbl conditional_question_no_second_question net_income_value_check dependent_question offered layear declaration] } context "when dependencies are not met" do it "returns an array of question keys whose pages conditions are not met" do