From 19fd4a6d843986576fca222c2ce618c9157d9280 Mon Sep 17 00:00:00 2001 From: CatalinVoineag <11318084+CatalinVoineag@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:42:06 +0000 Subject: [PATCH] Organisation index per service We want an organisation index for each service. This index page will be shown to the user after he sigin in as a persona only if the user has multiple organisations. An organisation can be a school or a provider, the Membership polymorphic table handles this. The Placements service will show both schools and providers, the claims only schools. --- Gemfile | 2 + Gemfile.lock | 3 + app/controllers/application_controller.rb | 10 ++- .../claims/organisations_controller.rb | 5 ++ .../placements/organisations_controller.rb | 6 ++ app/helpers/routes_helper.rb | 11 +++- app/models/membership.rb | 24 +++++++ app/models/provider.rb | 2 + app/models/school.rb | 2 + app/models/user.rb | 10 +++ app/views/claims/organisations/index.html.erb | 15 +++++ .../placements/organisations/index.html.erb | 27 ++++++++ config/routes/claims.rb | 2 + config/routes/placements.rb | 2 + .../20231220143008_create_memberships.rb | 10 +++ db/schema.rb | 13 +++- db/seeds.rb | 37 ++++++++++- spec/factories/memberships.rb | 26 ++++++++ spec/models/membership_spec.rb | 32 ++++++++++ spec/models/provider_spec.rb | 4 ++ spec/models/school_spec.rb | 7 ++- spec/models/user_spec.rb | 10 ++- spec/system/claims/view_organisations_spec.rb | 60 ++++++++++++++++++ .../placements/view_organisations_spec.rb | 62 +++++++++++++++++++ 24 files changed, 371 insertions(+), 11 deletions(-) create mode 100644 app/controllers/claims/organisations_controller.rb create mode 100644 app/controllers/placements/organisations_controller.rb create mode 100644 app/models/membership.rb create mode 100644 app/views/claims/organisations/index.html.erb create mode 100644 app/views/placements/organisations/index.html.erb create mode 100644 db/migrate/20231220143008_create_memberships.rb create mode 100644 spec/factories/memberships.rb create mode 100644 spec/models/membership_spec.rb create mode 100644 spec/system/claims/view_organisations_spec.rb create mode 100644 spec/system/placements/view_organisations_spec.rb diff --git a/Gemfile b/Gemfile index 1e6a5d305a..fb8784f8b4 100644 --- a/Gemfile +++ b/Gemfile @@ -89,6 +89,8 @@ group :test do gem "rails-controller-testing" gem "selenium-webdriver" gem "shoulda-matchers" + # launch browser when inspecting capybara specs + gem "launchy" end group :test, :development do diff --git a/Gemfile.lock b/Gemfile.lock index 980942ffec..aca0f862eb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -205,6 +205,8 @@ GEM kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) language_server-protocol (3.17.0.3) + launchy (2.5.2) + addressable (~> 2.8) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -525,6 +527,7 @@ DEPENDENCIES govuk-components (~> 5.0.0) govuk_design_system_formbuilder (~> 5.0.0) jsbundling-rails + launchy mail-notify omniauth omniauth-rails_csrf_protection diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8a33394711..37ec0b8e17 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -21,9 +21,13 @@ def current_user end def after_sign_in_path - return support_root_path if current_user.support_user? - - root_path + if current_user.support_user? + support_root_path + elsif current_user.memberships.many? + organisation_index_path + else + root_path + end end def after_sign_out_path diff --git a/app/controllers/claims/organisations_controller.rb b/app/controllers/claims/organisations_controller.rb new file mode 100644 index 0000000000..ca229cc8fd --- /dev/null +++ b/app/controllers/claims/organisations_controller.rb @@ -0,0 +1,5 @@ +class Claims::OrganisationsController < ApplicationController + def index + @schools = current_user.schools + end +end diff --git a/app/controllers/placements/organisations_controller.rb b/app/controllers/placements/organisations_controller.rb new file mode 100644 index 0000000000..3b848684eb --- /dev/null +++ b/app/controllers/placements/organisations_controller.rb @@ -0,0 +1,6 @@ +class Placements::OrganisationsController < ApplicationController + def index + @schools = current_user.schools + @providers = current_user.providers + end +end diff --git a/app/helpers/routes_helper.rb b/app/helpers/routes_helper.rb index f916d14868..40d26aabcd 100644 --- a/app/helpers/routes_helper.rb +++ b/app/helpers/routes_helper.rb @@ -2,14 +2,21 @@ module RoutesHelper def root_path { claims: claims_root_path, - placements: placements_root_path + placements: placements_root_path, }.fetch current_service end def support_root_path { claims: root_path, # TODO: claims support path in another PR - placements: placements_support_root_path + placements: placements_support_root_path, + }.fetch current_service + end + + def organisation_index_path + { + claims: claims_organisations_path, + placements: placements_organisations_path, }.fetch current_service end end diff --git a/app/models/membership.rb b/app/models/membership.rb new file mode 100644 index 0000000000..e158f1767f --- /dev/null +++ b/app/models/membership.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: memberships +# +# id :uuid not null, primary key +# organisation_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# organisation_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_memberships_on_organisation (organisation_type,organisation_id) +# index_memberships_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +class Membership < ApplicationRecord + belongs_to :user + belongs_to :organisation, polymorphic: true +end diff --git a/app/models/provider.rb b/app/models/provider.rb index 04dc0294e9..0d3eeb9f6e 100644 --- a/app/models/provider.rb +++ b/app/models/provider.rb @@ -12,6 +12,8 @@ # index_providers_on_provider_code (provider_code) UNIQUE # class Provider < ApplicationRecord + has_many :memberships, as: :organisation + validates :provider_code, presence: true validates :provider_code, uniqueness: { case_sensitive: false } end diff --git a/app/models/school.rb b/app/models/school.rb index 903d22acf6..bb72f2c502 100644 --- a/app/models/school.rb +++ b/app/models/school.rb @@ -17,6 +17,8 @@ # class School < ApplicationRecord belongs_to :gias_school, foreign_key: :urn, primary_key: :urn + has_many :memberships, as: :organisation + validates :urn, presence: true validates :urn, uniqueness: { case_sensitive: false } diff --git a/app/models/user.rb b/app/models/user.rb index c6e90b221a..d3b93b55fa 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,16 @@ # index_users_on_service_and_email (service,email) UNIQUE # class User < ApplicationRecord + has_many :memberships + has_many :schools, + through: :memberships, + source: :organisation, + source_type: "School" + has_many :providers, + through: :memberships, + source: :organisation, + source_type: "Provider" + enum :service, { no_service: "no_service", claims: "claims", placements: "placements" }, validate: true diff --git a/app/views/claims/organisations/index.html.erb b/app/views/claims/organisations/index.html.erb new file mode 100644 index 0000000000..334ea1ab74 --- /dev/null +++ b/app/views/claims/organisations/index.html.erb @@ -0,0 +1,15 @@ +
+
+

<%= t("organisations") %>

+ + <% if @schools.any? %> + + <% end %> +
+
diff --git a/app/views/placements/organisations/index.html.erb b/app/views/placements/organisations/index.html.erb new file mode 100644 index 0000000000..bbf8c8a273 --- /dev/null +++ b/app/views/placements/organisations/index.html.erb @@ -0,0 +1,27 @@ +
+
+

<%= t("organisations") %>

+ + <% if @schools.any? %> +

<%= t("schools") %>

+ + <% end %> + + <% if @providers.any? %> +

<%= t("providers") %>

+ + <% end %> +
+
diff --git a/config/routes/claims.rb b/config/routes/claims.rb index dda7a7692a..13281ec609 100644 --- a/config/routes/claims.rb +++ b/config/routes/claims.rb @@ -1,3 +1,5 @@ scope module: :claims, as: :claims, constraints: { host: ENV["CLAIMS_HOST"] } do root to: "pages#index" + + resources :organisations, only: [:index] end diff --git a/config/routes/placements.rb b/config/routes/placements.rb index 5ac869471e..55c69dddc1 100644 --- a/config/routes/placements.rb +++ b/config/routes/placements.rb @@ -9,4 +9,6 @@ root to: redirect("/support/organisations") resources :organisations, only: :index end + + resources :organisations, only: [:index] end diff --git a/db/migrate/20231220143008_create_memberships.rb b/db/migrate/20231220143008_create_memberships.rb new file mode 100644 index 0000000000..a7252de9e4 --- /dev/null +++ b/db/migrate/20231220143008_create_memberships.rb @@ -0,0 +1,10 @@ +class CreateMemberships < ActiveRecord::Migration[7.1] + def change + create_table :memberships, id: :uuid do |t| + t.references :user, null: false, foreign_key: true, type: :uuid + t.references :organisation, polymorphic: true, null: false, type: :uuid + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1d78e06008..f6c7890f7a 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.1].define(version: 2023_12_16_102842) do +ActiveRecord::Schema[7.1].define(version: 2023_12_20_143008) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -41,6 +41,16 @@ t.index ["urn"], name: "index_gias_schools_on_urn", unique: true end + create_table "memberships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "user_id", null: false + t.string "organisation_type", null: false + t.uuid "organisation_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["organisation_type", "organisation_id"], name: "index_memberships_on_organisation" + t.index ["user_id"], name: "index_memberships_on_user_id" + end + create_table "providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.string "provider_code", null: false t.datetime "created_at", null: false @@ -70,4 +80,5 @@ t.index ["service", "email"], name: "index_users_on_service_and_email", unique: true end + add_foreign_key "memberships", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index 866bc4b315..cfd868e974 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,6 +1,10 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# +SCHOOLS = [ + { claims: true }, + { placements: true }, + { claims: true, placements: true }, + { claims: true, placements: true }, +].freeze + # Persona Creation (Dummy User Creation) Rails.logger.debug "Creating Personas" @@ -12,3 +16,30 @@ end Rails.logger.debug "Personas successfully created!" + +Rake::Task["gias_update"].invoke unless GiasSchool.any? +gias_scools = GiasSchool.last(SCHOOLS.count) + +SCHOOLS.each.with_index do |school_attributes, index| + school = School.find_or_initialize_by(**school_attributes) + school.urn = gias_scools[index].urn + + school.save! +end + +User + .where(first_name: %w[Anne Patricia]) + .each do |user| + school = School.public_send(user.service).first + user.memberships.find_or_create_by!(organisation: school) + end + +User + .where(first_name: %w[Mary Colin]) + .each do |user| + schools = School.where("#{user.service}": true) + + schools.each do |school| + user.memberships.find_or_create_by!(organisation: school) + end + end diff --git a/spec/factories/memberships.rb b/spec/factories/memberships.rb new file mode 100644 index 0000000000..03e2fead86 --- /dev/null +++ b/spec/factories/memberships.rb @@ -0,0 +1,26 @@ +# == Schema Information +# +# Table name: memberships +# +# id :uuid not null, primary key +# organisation_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# organisation_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_memberships_on_organisation (organisation_type,organisation_id) +# index_memberships_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +FactoryBot.define do + factory :membership do + association :user + association :organisation, factory: :school + end +end diff --git a/spec/models/membership_spec.rb b/spec/models/membership_spec.rb new file mode 100644 index 0000000000..d973e67ce3 --- /dev/null +++ b/spec/models/membership_spec.rb @@ -0,0 +1,32 @@ +# == Schema Information +# +# Table name: memberships +# +# id :uuid not null, primary key +# organisation_type :string not null +# created_at :datetime not null +# updated_at :datetime not null +# organisation_id :uuid not null +# user_id :uuid not null +# +# Indexes +# +# index_memberships_on_organisation (organisation_type,organisation_id) +# index_memberships_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +require "rails_helper" + +RSpec.describe Membership, type: :model do + subject { create(:membership) } + + context "associations" do + it do + should belong_to(:user) + should belong_to(:organisation) + end + end +end diff --git a/spec/models/provider_spec.rb b/spec/models/provider_spec.rb index 6f591ecb12..44dc061221 100644 --- a/spec/models/provider_spec.rb +++ b/spec/models/provider_spec.rb @@ -14,6 +14,10 @@ require "rails_helper" RSpec.describe Provider, type: :model do + context "associations" do + it { should have_many(:memberships) } + end + context "validations" do subject { create(:provider) } diff --git a/spec/models/school_spec.rb b/spec/models/school_spec.rb index 5fe6ff44cd..54fbd88ebb 100644 --- a/spec/models/school_spec.rb +++ b/spec/models/school_spec.rb @@ -21,8 +21,9 @@ context "associations" do it do should belong_to(:gias_school).with_foreign_key(:urn).with_primary_key( - :urn + :urn, ) + should have_many(:memberships) end end @@ -33,6 +34,10 @@ it { is_expected.to validate_uniqueness_of(:urn).case_insensitive } end + context "delegations" do + it { should delegate_method(:name).to(:gias_school) } + end + context "scopes" do describe "services" do let!(:placements_on) { create(:school, placements: true) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d0933156c4..a592da1e48 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -20,11 +20,19 @@ RSpec.describe User, type: :model do subject { create(:user) } + context "associations" do + it do + should have_many(:memberships) + should have_many(:schools).through(:memberships).source(:organisation) + should have_many(:providers).through(:memberships).source(:organisation) + end + end + describe "validations" do it { is_expected.to validate_presence_of(:email) } it do is_expected.to validate_uniqueness_of(:email).scoped_to( - :service + :service, ).case_insensitive end it { is_expected.to validate_presence_of(:first_name) } diff --git a/spec/system/claims/view_organisations_spec.rb b/spec/system/claims/view_organisations_spec.rb new file mode 100644 index 0000000000..c0425fb289 --- /dev/null +++ b/spec/system/claims/view_organisations_spec.rb @@ -0,0 +1,60 @@ +require "rails_helper" + +RSpec.describe "View organisations", type: :system do + around do |example| + Capybara.app_host = "https://#{ENV["CLAIMS_HOST"]}" + example.run + Capybara.app_host = nil + end + + scenario "I sign in as persona Marry with multiple organistions" do + persona = given_the_claims_persona("Mary") + and_persona_has_multiple_organisations(persona) + when_i_visit_claims_personas + when_i_click_sign_in(persona) + i_can_see_a_list_marys_claims_organisations(persona) + end + + scenario "I sign in as persona Anne with one organisation" do + persona = given_the_claims_persona("Anne") + and_persona_has_one_organisation(persona) + when_i_visit_claims_personas + when_i_click_sign_in(persona) + i_am_redirected_to_the_root_path + end +end + +private + +def given_the_claims_persona(persona_name) + create(:persona, persona_name.downcase.to_sym, service: "claims") +end + +def and_persona_has_multiple_organisations(persona) + school1 = create(:school) + school2 = create(:school) + create(:membership, user: persona, organisation: school1) + create(:membership, user: persona, organisation: school2) +end + +def and_persona_has_one_organisation(persona) + create(:membership, user: persona, organisation: create(:school)) +end + +def when_i_visit_claims_personas + visit personas_path +end + +def when_i_click_sign_in(persona) + click_on "Sign In as #{persona.first_name}" +end + +def i_can_see_a_list_marys_claims_organisations(persona) + expect(page).to have_content("Organisations") + expect(page).to have_content(persona.schools.first.name) + expect(page).to have_content(persona.schools.last.name) +end + +def i_am_redirected_to_the_root_path + expect(page).to have_content("It works!") +end diff --git a/spec/system/placements/view_organisations_spec.rb b/spec/system/placements/view_organisations_spec.rb new file mode 100644 index 0000000000..825f89b98a --- /dev/null +++ b/spec/system/placements/view_organisations_spec.rb @@ -0,0 +1,62 @@ +require "rails_helper" + +RSpec.describe "View organisations", type: :system do + around do |example| + Capybara.app_host = "https://#{ENV["PLACEMENTS_HOST"]}" + example.run + Capybara.app_host = nil + end + + scenario "I sign in as persona Marry with multiple organistions" do + persona = given_the_placements_persona("Mary") + and_persona_has_multiple_organisations(persona) + when_i_visit_placements_personas + when_i_click_sign_in(persona) + i_can_see_a_list_marys_placements_organisations(persona) + end + + scenario "I sign in as persona Anne with one organisation" do + persona = given_the_placements_persona("Anne") + and_persona_has_one_organisation(persona) + when_i_visit_placements_personas + when_i_click_sign_in(persona) + i_am_redirected_to_the_root_path + end +end + +private + +def given_the_placements_persona(persona_name) + create(:persona, persona_name.downcase.to_sym, service: "placements") +end + +def and_persona_has_multiple_organisations(persona) + school1 = create(:school) + provider = create(:provider) + create(:membership, user: persona, organisation: school1) + create(:membership, user: persona, organisation: provider) +end + +def and_persona_has_one_organisation(persona) + create(:membership, user: persona, organisation: create(:school)) +end + +def when_i_visit_placements_personas + visit personas_path +end + +def when_i_click_sign_in(persona) + click_on "Sign In as #{persona.first_name}" +end + +def i_can_see_a_list_marys_placements_organisations(persona) + expect(page).to have_content("Organisations") + expect(page).to have_content("Schools") + expect(page).to have_content("Providers") + expect(page).to have_content(persona.schools.first.name) + expect(page).to have_content(persona.providers.first.provider_code) +end + +def i_am_redirected_to_the_root_path + expect(page).to have_content("It works!") +end