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? %>
+
+ <% @schools.each do |school| %>
+ -
+ <%= govuk_link_to school.name, request.env["PATH_INFO"], no_visited_state: true %>
+
+ <% end %>
+
+ <% 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") %>
+
+ <% @schools.each do |school| %>
+ -
+ <%= govuk_link_to school.name, request.env["PATH_INFO"], no_visited_state: true %>
+
+ <% end %>
+
+ <% end %>
+
+ <% if @providers.any? %>
+
<%= t("providers") %>
+
+ <% @providers.each do |provider| %>
+ -
+ <%= govuk_link_to provider.provider_code, request.env["PATH_INFO"], no_visited_state: true %>
+
+ <% end %>
+
+ <% 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