mirror of
https://github.com/edcommonwealth/sqm-dashboards.git
synced 2026-03-07 21:48:16 -08:00
Show modal when no measures for a school/year have meet their threshold
This commit is contained in:
parent
edeb3f4e59
commit
cf6e80ce6b
18 changed files with 220 additions and 21 deletions
|
|
@ -30,6 +30,9 @@ $input-border-radius: 0;
|
|||
$input-btn-focus-color-opacity: 0.35;
|
||||
$btn-border-radius: 0;
|
||||
|
||||
$btn-close-width: 1.5em;
|
||||
$btn-close-opacity: 1;
|
||||
|
||||
$nav-link-color: $white;
|
||||
$nav-link-font-weight: 600;
|
||||
$nav-link-hover-color: $nav-link-color;
|
||||
|
|
@ -48,3 +51,6 @@ $popover-border-color: $gray-2;
|
|||
$popover-border-radius: 4px;
|
||||
$popover-body-padding-x: map-get($spacers, 4);
|
||||
$popover-body-padding-y: map-get($spacers, 4);
|
||||
|
||||
$modal-header-border-color: transparent;
|
||||
$modal-content-border-radius: 0;
|
||||
|
|
|
|||
|
|
@ -50,3 +50,7 @@
|
|||
.popover {
|
||||
box-shadow: 0 0 8px rgba($black, 0.25);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 40px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ class DashboardController < SqmApplicationController
|
|||
|
||||
def index
|
||||
@variance_chart_row_presenters = Measure.all.map(&method(:presenter_for_measure))
|
||||
|
||||
@category_presenters = SqmCategory.sorted.map { |sqm_category| CategoryPresenter.new(category: sqm_category) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class SqmApplicationController < ActionController::Base
|
|||
@school = School.find_by_slug school_slug
|
||||
@schools = School.where(district: @district).sort_by(&:name)
|
||||
@academic_year = AcademicYear.find_by_range params[:year]
|
||||
@has_empty_dataset = Measure.none_meet_threshold? school: @school, academic_year: @academic_year
|
||||
end
|
||||
|
||||
def district_slug
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ Turbolinks.start()
|
|||
ActiveStorage.start()
|
||||
import { initializeListenersForNavDropdowns, initializePopovers } from "./dashboard"
|
||||
import { initializeListenersForHomeDropdowns } from "./home"
|
||||
import { showEmptyDatasetModal } from "./modal"
|
||||
|
||||
document.addEventListener("turbolinks:load", () => {
|
||||
initializeListenersForNavDropdowns()
|
||||
initializeListenersForHomeDropdowns()
|
||||
initializePopovers()
|
||||
showEmptyDatasetModal()
|
||||
})
|
||||
|
|
|
|||
8
app/javascript/modal.js
Normal file
8
app/javascript/modal.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Modal } from "bootstrap";
|
||||
|
||||
export function showEmptyDatasetModal() {
|
||||
const modal = document.querySelector('.modal');
|
||||
if(modal){
|
||||
new Modal(modal).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,10 @@ class Measure < ActiveRecord::Base
|
|||
|
||||
scope :source_includes_survey_items, ->() { joins(:survey_items).uniq }
|
||||
|
||||
def self.none_meet_threshold?(school:, academic_year:)
|
||||
none? { |measure| SurveyItemResponse.sufficient_data?(measure: measure, school: school, academic_year: academic_year) }
|
||||
end
|
||||
|
||||
def teacher_survey_items
|
||||
@teacher_survey_items ||= survey_items.where("survey_item_id LIKE 't-%'")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ class SqmCategory < ActiveRecord::Base
|
|||
scope :sorted, ->() { order(:sort_index) }
|
||||
|
||||
has_many :subcategories
|
||||
has_many :measures, through: :subcategories
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ class SurveyItemResponse < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.sufficient_data?(measure:, school:, academic_year:)
|
||||
meets_teacher_threshold = teacher_sufficient_data?(measure: measure, school: school, academic_year: academic_year)
|
||||
meets_student_threshold = student_sufficient_data?(measure: measure, school: school, academic_year: academic_year)
|
||||
meets_teacher_threshold || meets_student_threshold
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.for_measure_meeting_threshold(measure:, school:, academic_year:)
|
||||
|
|
@ -46,11 +52,6 @@ class SurveyItemResponse < ActiveRecord::Base
|
|||
scope :teacher_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 't-%'") }
|
||||
scope :student_responses_for_measure, ->(measure) { for_measure(measure).where("survey_items.survey_item_id LIKE 's-%'") }
|
||||
|
||||
def self.sufficient_data?(measure:, school:, academic_year:)
|
||||
meets_teacher_threshold = teacher_sufficient_data?(measure: measure, school: school, academic_year: academic_year)
|
||||
meets_student_threshold = student_sufficient_data?(measure: measure, school: school, academic_year: academic_year)
|
||||
meets_teacher_threshold || meets_student_threshold
|
||||
end
|
||||
|
||||
def self.student_sufficient_data?(measure:, school:, academic_year:)
|
||||
if measure.includes_student_survey_items?
|
||||
|
|
|
|||
20
app/views/layouts/sqm/_empty_dataset_modal.html.erb
Normal file
20
app/views/layouts/sqm/_empty_dataset_modal.html.erb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<div id="empty-dataset-modal" class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">No results available</h3>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>We’re unable to display results for the selected school and school year due to one or more of the following reasons:</p>
|
||||
<ul>
|
||||
<li>Limited availability of school admin data</li>
|
||||
<li>Low teacher and/or student survey response rates </li>
|
||||
<li>Limited or no historical data for this school or district</li>
|
||||
</ul>
|
||||
<p>You may continue to explore the structure of the MCIEA School Quality Measures Framework if you like, but we recommend selecting a different school and/or school year for the best experience. </p>
|
||||
<p>You’re also welcome to <a href="https://www.mciea.org/contact.html" target="_blank">contact MCIEA</a> to help ensure this school has data for you to review next year.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -44,5 +44,7 @@
|
|||
<%= render partial: 'layouts/sqm/footer' %>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'layouts/sqm/empty_dataset_modal' if @has_empty_dataset %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
BIN
schooldashfeaturespec.png
Normal file
BIN
schooldashfeaturespec.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
|
|
@ -20,7 +20,7 @@ FactoryBot.define do
|
|||
name { "A #{rand} category" }
|
||||
category_id { rand.to_s }
|
||||
description { "A description of a category" }
|
||||
slug { "a-#{rand}-category" }
|
||||
slug { name.parameterize }
|
||||
sort_index { 1 }
|
||||
end
|
||||
|
||||
|
|
|
|||
49
spec/javascript/modal.spec.js
Normal file
49
spec/javascript/modal.spec.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { showEmptyDatasetModal } from "modal";
|
||||
|
||||
describe("Empty data set modal", () => {
|
||||
describe("When a modal element exists on the page", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `<html><body>
|
||||
<div class="modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Modal title</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Modal body text goes here.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body> </html> `;
|
||||
});
|
||||
|
||||
it("Adds a class to make the modal visible", () => {
|
||||
showEmptyDatasetModal();
|
||||
const modal = document.querySelector(".modal");
|
||||
expect(modal.classList.contains('show')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("When a modal doesn't exist on the page", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<html>
|
||||
<body></body>
|
||||
</html>
|
||||
`
|
||||
})
|
||||
|
||||
it("ignores the content", () =>{
|
||||
showEmptyDatasetModal();
|
||||
const modal = document.querySelector(".modal");
|
||||
expect(modal).toBe(null);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
@ -54,7 +54,12 @@ RSpec.configure do |config|
|
|||
|
||||
config.include Capybara::DSL
|
||||
|
||||
config.before(:each, type: :system) do
|
||||
driven_by :rack_test
|
||||
end
|
||||
|
||||
config.before(:each, js: true) do
|
||||
driven_by :apparition
|
||||
Capybara.default_max_wait_time = 10
|
||||
Capybara.page.driver.resize(3000, 3000)
|
||||
end
|
||||
|
|
|
|||
43
spec/system/authentication_spec.rb
Normal file
43
spec/system/authentication_spec.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'authentication' do
|
||||
let(:district) { create(:district) }
|
||||
let(:school) { create(:school, district: district) }
|
||||
let(:academic_year) { create(:academic_year) }
|
||||
|
||||
context 'when using the wrong credentials' do
|
||||
before :each do
|
||||
page.driver.browser.basic_authorize('wrong username', 'wrong password')
|
||||
end
|
||||
it 'does not show any information' do
|
||||
visit dashboard_path
|
||||
|
||||
expect(page).not_to have_text(school.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using the right credentials' do
|
||||
before :each do
|
||||
page.driver.browser.basic_authorize(username, password)
|
||||
end
|
||||
it 'does show information' do
|
||||
visit dashboard_path
|
||||
|
||||
expect(page).to have_text(school.name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def username
|
||||
district.name.downcase
|
||||
end
|
||||
def password
|
||||
"#{username}!"
|
||||
end
|
||||
|
||||
def dashboard_path
|
||||
district_school_dashboard_index_path(district, school, year: academic_year.range)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
require 'rails_helper'
|
||||
|
||||
feature 'School dashboard', type: feature do
|
||||
describe 'District Admin', js: true do
|
||||
let(:district) { District.find_by_slug 'winchester' }
|
||||
let(:different_district) { District.find_by_slug 'boston' }
|
||||
let(:school) { School.find_by_slug 'winchester-high-school' }
|
||||
|
|
@ -60,19 +60,7 @@ feature 'School dashboard', type: feature do
|
|||
SurveyItemResponse.import survey_item_responses
|
||||
end
|
||||
|
||||
after :each do
|
||||
DatabaseCleaner.clean
|
||||
end
|
||||
|
||||
scenario 'User authentication fails' do
|
||||
page.driver.browser.basic_authorize('wrong username', 'wrong password')
|
||||
|
||||
visit "/districts/#{district.slug}/schools/#{school.slug}/dashboard?year=2020-21"
|
||||
|
||||
expect(page).not_to have_text(school.name)
|
||||
end
|
||||
|
||||
scenario 'District Admin navigates the site', js: true do
|
||||
it 'navigates through the site' do
|
||||
page.driver.basic_authorize(username, password)
|
||||
|
||||
visit '/welcome'
|
||||
|
|
@ -132,6 +120,16 @@ def go_to_different_district(district)
|
|||
select district.name, from: 'select-district'
|
||||
end
|
||||
|
||||
def go_to_browse_page_for_school_without_data(school)
|
||||
click_on 'Browse'
|
||||
select school.name, from: 'select-school'
|
||||
end
|
||||
|
||||
def go_to_dashboard_page_for_school_without_data(school)
|
||||
click_on 'Dashboard'
|
||||
select school.name, from: 'select-school'
|
||||
end
|
||||
|
||||
def district_admin_sees_schools_change
|
||||
expected_path = "/districts/#{school_in_same_district.district.slug}/schools/#{school_in_same_district.slug}/dashboard?year=2020-21"
|
||||
expect(page).to have_current_path(expected_path)
|
||||
56
spec/system/sqm_application_spec.rb
Normal file
56
spec/system/sqm_application_spec.rb
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'SQM Application' do
|
||||
let(:district) { create(:district) }
|
||||
let(:school) { create(:school, district: district) }
|
||||
let(:academic_year) { create(:academic_year) }
|
||||
let(:category) { create(:sqm_category) }
|
||||
let(:measure) { create(:measure) }
|
||||
|
||||
before :each do
|
||||
driven_by :rack_test
|
||||
page.driver.browser.basic_authorize(username, password)
|
||||
end
|
||||
|
||||
context 'when no measures meet their threshold' do
|
||||
it 'shows a modal on all pages' do
|
||||
[dashboard_path, browse_path].each do |path|
|
||||
visit path
|
||||
expect(page).to have_css '.modal'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'at least one measure meets its threshold' do
|
||||
before :each do
|
||||
teacher_survey_item = create(:teacher_survey_item, measure: measure)
|
||||
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD,
|
||||
survey_item: teacher_survey_item, academic_year: academic_year, school: school)
|
||||
end
|
||||
|
||||
it 'does not show a modal on any page' do
|
||||
[dashboard_path, browse_path].each do |path|
|
||||
visit path
|
||||
expect(page).not_to have_css '.modal'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def username
|
||||
district.name.downcase
|
||||
end
|
||||
|
||||
def password
|
||||
"#{username}!"
|
||||
end
|
||||
|
||||
def dashboard_path
|
||||
district_school_dashboard_index_path(district, school, year: academic_year.range)
|
||||
end
|
||||
|
||||
def browse_path
|
||||
district_school_sqm_category_path(district, school, category, year: academic_year.range)
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue