Replace javascript logic with hotwire. Also hide district dropdown on

home page if there is only one district.
This commit is contained in:
rebuilt 2022-12-10 14:07:58 -08:00
parent 87802c034e
commit 69179ce157
16 changed files with 3223 additions and 1259 deletions

View file

@ -2,10 +2,53 @@
class HomeController < ApplicationController class HomeController < ApplicationController
helper HeaderHelper helper HeaderHelper
def index
@districts = District.all.order(:name)
@schools = School.all.includes([:district]).order(:name)
def index
@districts = districts
@district = district
@schools = schools
@school = school
@year = year
@categories = Category.sorted.map { |category| CategoryPresenter.new(category:) } @categories = Category.sorted.map { |category| CategoryPresenter.new(category:) }
end end
private
def districts
District.all.order(:name).map do |district|
[district.name, district.id]
end
end
def district
return District.first if District.count == 1
District.find(params[:district]) if params[:district].present?
end
def schools
if district.present?
district.schools.order(:name).map do |school|
[school.name, school.id]
end
else
[]
end
end
def school
School.find(params[:school]) if params[:school].present?
end
def year
latest_response_rate = ResponseRate.where(school:)
.where('meets_student_threshold = ? or meets_teacher_threshold = ?', true, true)
.joins('inner join academic_years a on response_rates.academic_year_id=a.id')
.order('a.range DESC').first
academic_year = latest_response_rate.academic_year.range if latest_response_rate.present?
academic_year || AcademicYear.order('range DESC').first.range
end
end end

View file

@ -34,26 +34,6 @@ module HeaderHelper
end end
end end
def school_mapper(school)
academic_year = latest_year(school)
{
name: school.name,
district_id: school.district_id,
url: district_school_overview_index_path(school.district, school,
{ year: academic_year.range })
}
end
def latest_year(school)
latest_response_rate = ResponseRate.where(school:)
.where('meets_student_threshold = ? or meets_teacher_threshold = ?', true, true)
.joins('inner join academic_years a on response_rates.academic_year_id=a.id')
.order('a.range DESC').first
academic_year = latest_response_rate.academic_year if latest_response_rate.present?
academic_year || AcademicYear.order('range DESC').first
end
def link_weight(path:) def link_weight(path:)
active?(path:) ? 'weight-700' : 'weight-400' active?(path:) ? 'weight-700' : 'weight-400'
end end

View file

@ -9,12 +9,10 @@ import {
initializeListenersForNavDropdowns, initializeListenersForNavDropdowns,
initializePopovers, initializePopovers,
} from "./overview"; } from "./overview";
import { initializeListenersForHomeDropdowns } from "./home";
import { showEmptyDatasetModal } from "./modal"; import { showEmptyDatasetModal } from "./modal";
document.addEventListener("turbo:load", () => { document.addEventListener("turbo:load", () => {
initializeListenersForNavDropdowns(); initializeListenersForNavDropdowns();
initializeListenersForHomeDropdowns();
initializePopovers(); initializePopovers();
showEmptyDatasetModal(); showEmptyDatasetModal();
}); });

View file

@ -0,0 +1,13 @@
import { Controller } from "@hotwired/stimulus"
import debounce from "debounce";
// Connects to data-controller="form"
export default class extends Controller {
initialize() {
this.submit = debounce(this.submit.bind(this), 300)
}
submit() {
this.element.requestSubmit();
}
}

View file

@ -6,3 +6,6 @@ import { application } from "./application"
import AnalyzeController from "./analyze_controller.js" import AnalyzeController from "./analyze_controller.js"
application.register("analyze", AnalyzeController) application.register("analyze", AnalyzeController)
import FormController from "./form_controller.js"
application.register("form", FormController)

View file

@ -1,49 +0,0 @@
import 'bootstrap';
export function initializeListenersForHomeDropdowns() {
const districtDropdown = document.querySelector("#district-dropdown");
if (districtDropdown) {
const schoolDropdown = document.querySelector("#school-dropdown");
districtDropdown.addEventListener("change", (event) => {
const districtId = Number(event.target.value);
const schoolsInDistrict = window.schools.filter(
(school) => school.district_id === districtId
);
schoolDropdown.replaceChildren(
...schoolsInDistrict.map((school) => {
return createOptionForSelect(school.name, school.url, false);
})
);
let optionElem = createOptionForSelect("Select a school", schoolDropdown.firstChild.value, true)
schoolDropdown.insertBefore(optionElem, schoolDropdown.firstChild);
schoolDropdown.disabled = false;
});
schoolDropdown.addEventListener("change", (event) => {
const goButton = document.querySelector('button[data-id="go-to-school"]');
goButton.disabled = false;
});
document
.querySelector('button[data-id="go-to-school"]')
.addEventListener("click", (event) => {
const selectedSchoolURL = schoolDropdown.value;
window.location = selectedSchoolURL;
});
}
}
function createOptionForSelect(name, value, selected) {
const optionElem = document.createElement("option");
optionElem.setAttribute("value", value);
if (selected === true) {
optionElem.setAttribute("selected", "selected");
}
const schoolNameNode = document.createTextNode(name);
optionElem.appendChild(schoolNameNode);
return optionElem;
}

View file

@ -2,20 +2,46 @@
<div class="hero-text"> <div class="hero-text">
<h1 class="sub-header">School Quality Measures Dashboard</h1> <h1 class="sub-header">School Quality Measures Dashboard</h1>
<p class="hero-description">A school quality framework with multiple measures that offers a fair and comprehensive <p class="hero-description">A school quality framework with multiple measures that offers a fair and comprehensive
picture of school performance</p> picture of school performance</p>
</div> </div>
</div> </div>
<div class="bg-color-blue"> <div class="bg-color-blue">
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-6 welcome-controls d-flex justify-content-center"> <div class="col-6 welcome-controls d-flex justify-content-center">
<%= collection_select(:district, :id, @districts, :id, :name, { prompt: 'Select a district', selected: "" }, { id: 'district-dropdown', 'data-id': 'district-dropdown', class: 'form-select' }) %>
<select id="school-dropdown" class="mx-3 form-select" data-id="school-dropdown" disabled> <%= form_with(url: welcome_path, method: :get,
<option value="Select a school">Select a school</option> data: {
</select> turbo_frame: "schools",
turbo_action: "advance",
controller: "form",
action: "input->form#submit"
}) do |f| %>
<%= turbo_frame_tag "schools" do %>
<div class="d-flex">
<% if District.count > 1 %>
<div class="row">
<%= f.select :district, @districts,
{include_blank: "Select a District", selected: params[:district] } , {id: "district-dropdown", class: "form-select", hidden: @districts.count == 1} %>
</div>
<% end %>
<div>
<%= f.select :school, @schools,
{include_blank: "Select a School", selected: params[:school]}, { id: "school-dropdown", class: "form-select mx-3"} if @schools %>
</div>
<% if @school.present? %>
<%= link_to "Go", district_school_overview_index_path(@district, @school, {year: @year} ), class: "mx-4 btn btn-secondary" , data: {turbo_frame: "_top"} %>
<% else %>
<%= button_to "Go", "/", class: "mx-4 btn btn-secondary" , data: {turbo_frame: "_top"}, disabled: true %>
<% end %>
</div>
<% end %>
<% end %>
<button data-id="go-to-school" class="mx-3 btn btn-secondary" disabled>Go</button>
</div> </div>
</div> </div>
</div> </div>
@ -30,8 +56,8 @@
</div> </div>
<p class="mt-5">The School Quality Measures Framework aims to describe the full measure of what makes a good <p class="mt-5">The School Quality Measures Framework aims to describe the full measure of what makes a good
school. The three outer categories are essential inputs to school quality that influence the center two key school. The three outer categories are essential inputs to school quality that influence the center two key
outcomes.</p> outcomes.</p>
</div> </div>
<div class="col-lg-7"> <div class="col-lg-7">
<h2 class="mb-4">School Quality Measures Framework</h2> <h2 class="mb-4">School Quality Measures Framework</h2>

View file

@ -27,8 +27,5 @@
<%= render partial: 'layouts/footer' %> <%= render partial: 'layouts/footer' %>
</div> </div>
<script>
window.schools = <%= @schools.map{ |school| school_mapper(school) }.to_json.html_safe %>;
</script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,8 @@
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"esbuild": "^0.13.6", "esbuild": "^0.13.6",
"sass": "^1.43.4" "sass": "^1.43.4",
"debounce": "^1.2.1"
}, },
"scripts": { "scripts": {
"build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds", "build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds",

View file

@ -4,6 +4,11 @@ describe HomeController, type: :controller do
let!(:categories) do let!(:categories) do
[create(:category, name: 'Second', sort_index: 2), create(:category, name: 'First', sort_index: 1)] [create(:category, name: 'Second', sort_index: 2), create(:category, name: 'First', sort_index: 1)]
end end
let(:academic_year) { create(:academic_year) }
before :each do
academic_year
end
it 'fetches categories sorted by sort_index' do it 'fetches categories sorted by sort_index' do
get :index get :index

View file

@ -1,94 +0,0 @@
import { initializeListenersForHomeDropdowns } from "home";
describe("School selection and go button", () => {
let changeDistrict, changeSchool, clickGo;
beforeEach(() => {
window.schools = [
{
district_id: 1,
url: "/school1url",
name: "school 1 name",
},
{
district_id: 2,
url: "/school2url",
name: "school 2 name",
},
];
document.body.innerHTML = `
<div>
<select id="school-dropdown" data-id="school-dropdown" disabled></select>
<select id="district-dropdown" data-id="district-dropdown">
<option value="1">District 1</option>
<option value="2">District 2</option>
</select>
<button data-id="go-to-school" disabled></button>
</div>`;
const districtDropdown = document.getElementById("district-dropdown");
changeDistrict = (districtName) => {
districtDropdown.value = Array.from(districtDropdown.children).find(
(element) => element.innerHTML === districtName
).value;
const event = new Event("change");
districtDropdown.dispatchEvent(event);
};
changeSchool = (schoolName) => {
const schoolDropdown = document.getElementById("school-dropdown");
schoolDropdown.value = Array.from(schoolDropdown.children).find(
(element) => element.innerHTML === schoolName
).value;
const event = new Event("change");
schoolDropdown.dispatchEvent(event);
};
clickGo = () => {
const goButton = document.querySelector('button[data-id="go-to-school"]');
const clickEvent = new Event("click");
goButton.dispatchEvent(clickEvent);
};
initializeListenersForHomeDropdowns();
});
it("populates school dropdown only with schools from the selected district and the prompt", () => {
const schoolDropdown = document.getElementById("school-dropdown");
changeDistrict("District 1");
expect(schoolDropdown.firstChild.innerHTML).toBe("Select a school");
expect(schoolDropdown.childElementCount).toBe(2);
changeDistrict("District 2");
expect(schoolDropdown.childElementCount).toBe(2);
expect(Array.from(schoolDropdown.options).map((o) => o.text)).toContain(
"school 2 name"
);
});
it("Clicking the go button redirects to the school url", () => {
delete window.location;
window.location = "";
changeDistrict("District 1");
changeSchool("school 1 name");
clickGo();
expect(window.location).toBe("/school1url");
});
it("enables School dropdown once a district is selected", () => {
const schoolDropdown = document.getElementById("school-dropdown");
changeDistrict("District 1");
expect(schoolDropdown.disabled).toBe(false);
});
it("enables Go button once a school is selected", () => {
changeDistrict("District 1");
changeSchool("school 1 name");
const goButton = document.querySelector('button[data-id="go-to-school"]');
expect(goButton.disabled).toBe(false);
});
});

View file

@ -163,8 +163,13 @@ def district_admin_sees_problem_solving_emphasis
end end
def go_to_school_overview_from_welcome_page(district, school) def go_to_school_overview_from_welcome_page(district, school)
expect(page).to have_select('district', selected: 'Select a District')
select district.name, from: 'district-dropdown' select district.name, from: 'district-dropdown'
expect(page).to have_select('school', selected: 'Select a School')
visit welcome_path({ district: district.id, school: school.id })
expect(page).to have_select('school', selected: 'Winchester High School')
select school.name, from: 'school-dropdown' select school.name, from: 'school-dropdown'
click_on 'Go' click_on 'Go'
end end

View file

@ -1,6 +1,8 @@
require 'rails_helper' require 'rails_helper'
describe 'home/index' do describe 'home/index' do
let(:school) { create(:school) }
let(:district) { create(:district) }
subject { Nokogiri::HTML(rendered) } subject { Nokogiri::HTML(rendered) }
before :each do before :each do

File diff suppressed because it is too large Load diff

View file

@ -2205,6 +2205,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0" whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0" whatwg-url "^8.0.0"
debounce@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@4, debug@^4.1.0, debug@^4.1.1: debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.3.4" version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"