feat: Add parent button to overview page and alter 'School Quality Framework Indicators' section to show parent scales

mciea-main
Nelson Jovel 1 year ago
parent ddac10f9e5
commit a391acc554

@ -54,6 +54,15 @@
grid-template-rows: repeat(4, max-content); grid-template-rows: repeat(4, max-content);
} }
.school-quality-frameworks-parent {
display: grid;
row-gap: map-get($spacers, 4);
column-gap: map-get($spacers, 5);
grid-auto-flow: column;
grid-template-columns: 250px 250px 250px;
grid-template-rows: repeat(4, max-content);
}
.zone-header { .zone-header {
@extend .sub-header-4; @extend .sub-header-4;
} }

@ -3,7 +3,7 @@
class AnalyzeController < SqmApplicationController class AnalyzeController < SqmApplicationController
def index def index
@presenter = Analyze::Presenter.new(params:, school: @school, academic_year: @academic_year) @presenter = Analyze::Presenter.new(params:, school: @school, academic_year: @academic_year)
@background ||= BackgroundPresenter.new(num_of_columns: @presenter.graph.columns.count) @background ||= Analyze::BackgroundPresenter.new(num_of_columns: @presenter.graph.columns.count)
@academic_year = @presenter.selected_academic_years&.first || AcademicYear.last @academic_year = @presenter.selected_academic_years&.first || AcademicYear.last
end end
end end

@ -1,42 +1,19 @@
# frozen_string_literal: true # frozen_string_literal: true
class OverviewController < SqmApplicationController class OverviewController < SqmApplicationController
before_action :check_empty_dataset, only: [:index]
helper VarianceHelper helper VarianceHelper
def index def index
@variance_chart_row_presenters = measures.map(&method(:presenter_for_measure)) @page = if params[:view] == "student" || params[:view].nil?
@category_presenters = categories.map { |category| CategoryPresenter.new(category:) } Overview::OverviewPresenter.new(params:, school: @school, academic_year: @academic_year)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school, else
academic_year: @academic_year) Overview::ParentOverviewPresenter.new(params:, school: @school, academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school, end
academic_year: @academic_year)
end @has_empty_dataset = @page.empty_dataset?
@variance_chart_row_presenters = @page.variance_chart_row_presenters
private @category_presenters = @page.category_presenters
@student_response_rate_presenter = @page.student_response_rate_presenter
def presenter_for_measure(measure) @teacher_response_rate_presenter = @page.teacher_response_rate_presenter
score = measure.score(school: @school, academic_year: @academic_year)
VarianceChartRowPresenter.new(measure:, score:)
end
def check_empty_dataset
@has_empty_dataset = subcategories.none? do |subcategory|
response_rate = subcategory.response_rate(school: @school, academic_year: @academic_year)
response_rate.meets_student_threshold || response_rate.meets_teacher_threshold
end
end
def measures
@measures ||= subcategories.flat_map(&:measures)
end
def subcategories
@subcategories ||= categories.flat_map(&:subcategories)
end
def categories
@categories ||= Category.sorted.includes(%i[measures scales admin_data_items subcategories])
end end
end end

@ -2,6 +2,7 @@
class Scale < ApplicationRecord class Scale < ApplicationRecord
belongs_to :measure, counter_cache: true belongs_to :measure, counter_cache: true
has_one :category, through: :measure
has_many :survey_items has_many :survey_items
has_many :survey_item_responses, through: :survey_items has_many :survey_item_responses, through: :survey_items
has_many :admin_data_items has_many :admin_data_items
@ -19,6 +20,18 @@ class Scale < ApplicationRecord
@score[[school, academic_year]] @score[[school, academic_year]]
end end
def parent_score(school:, academic_year:)
@parent_score ||= Hash.new do |memo, (school, academic_year)|
memo[[school, academic_year]] = begin
items = []
items << collect_survey_item_average(survey_items.parent_survey_items, school, academic_year)
items.remove_blanks.average
end
end
@parent_score[[school, academic_year]]
end
scope :teacher_scales, lambda { scope :teacher_scales, lambda {
where("scale_id LIKE 't-%'") where("scale_id LIKE 't-%'")
} }

@ -1,4 +1,4 @@
class BackgroundPresenter class Analyze::BackgroundPresenter
include AnalyzeHelper include AnalyzeHelper
attr_reader :num_of_columns attr_reader :num_of_columns

@ -45,6 +45,23 @@ class CategoryPresenter
end end
end end
def harvey_scorecard_presenters(school:, academic_year:)
@category.subcategories.map do |subcategory|
measures = subcategory.measures
zones = Zones.new(
watch_low_benchmark: measures.map(&:watch_low_benchmark).average,
growth_low_benchmark: measures.map(&:growth_low_benchmark).average,
approval_low_benchmark: measures.map(&:approval_low_benchmark).average,
ideal_low_benchmark: measures.map(&:ideal_low_benchmark).average
)
Overview::ScorecardPresenter.new(construct: subcategory,
zones:,
score: subcategory.score(school:, academic_year:),
id: subcategory.subcategory_id)
end
end
def to_model def to_model
@category @category
end end
@ -52,18 +69,18 @@ class CategoryPresenter
private private
def colors def colors
{ 'Teachers & Leadership': 'blue', { '1': "blue",
'School Culture': 'red', '2': "red",
'Resources': 'black', '3': "black",
'Academic Learning': 'lime', '4': "lime",
'Community & Wellbeing': 'teal' } '5': "teal" }
end end
def classes def classes
{ 'Teachers & Leadership': 'apple-alt', { '1': "apple-alt",
'School Culture': 'school', '2': "school",
'Resources': 'users-cog', '3': "users-cog",
'Academic Learning': 'graduation-cap', '4': "graduation-cap",
'Community & Wellbeing': 'heart' } '5': "heart" }
end end
end end

@ -9,6 +9,7 @@ class MeasurePresenter
@school = school @school = school
@name = measure.name @name = measure.name
@description = measure.description @description = measure.description
@id = measure.measure_id
end end
def title def title

@ -0,0 +1,58 @@
class Overview::OverviewPresenter
attr_reader :view, :school, :academic_year
def initialize(params:, school:, academic_year:)
@view = params[:view] || "student"
@school = school
@academic_year = academic_year
end
def variance_chart_row_presenters
measures.map(&method(:presenter_for_measure))
end
def category_presenters
categories.map { |category| CategoryPresenter.new(category:) }
end
def measures
@measures ||= subcategories.flat_map(&:measures)
end
def subcategories
@subcategories ||= categories.flat_map(&:subcategories)
end
def framework_indicator_class
"school-quality-frameworks"
end
def show_response_rates
view == "student"
end
def categories
Category.sorted.includes(%i[measures scales admin_data_items subcategories])
end
def student_response_rate_presenter
ResponseRatePresenter.new(focus: :student, school: @school, academic_year: @academic_year)
end
def teacher_response_rate_presenter
ResponseRatePresenter.new(focus: :teacher, school: @school, academic_year: @academic_year)
end
def presenter_for_measure(measure)
score = measure.score(school: @school, academic_year: @academic_year)
Overview::VarianceChartRowPresenter.new(measure:, score:)
end
def empty_dataset?
subcategories.none? do |subcategory|
response_rate = subcategory.response_rate(school: @school, academic_year: @academic_year)
response_rate.meets_student_threshold || response_rate.meets_teacher_threshold
end
end
end

@ -0,0 +1,15 @@
class Overview::ParentOverviewPresenter < Overview::OverviewPresenter
def categories
Category.sorted.includes(%i[measures scales admin_data_items subcategories]).select do |category|
category.survey_items.parent_survey_items.count.positive?
end
end
def category_presenters
categories.map { |category| ParentCategoryPresenter.new(category:) }
end
def framework_indicator_class
"school-quality-frameworks-parent"
end
end

@ -1,13 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class SubcategoryCardPresenter class Overview::ScorecardPresenter
attr_reader :name, :subcategory, :category, :subcategory_id attr_reader :name, :construct, :category, :id
def initialize(subcategory:, zones:, score:) def initialize(construct:, zones:, score:, id:)
@name = subcategory.name @name = construct.name
@subcategory = subcategory @category = construct.category
@category = subcategory.category @id = id
@subcategory_id = subcategory.subcategory_id
@zones = zones @zones = zones
@score = score @score = score
end end
@ -25,7 +24,7 @@ class SubcategoryCardPresenter
end end
def to_model def to_model
subcategory construct
end end
private private

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class VarianceChartRowPresenter class Overview::VarianceChartRowPresenter
include Comparable include Comparable
attr_reader :score, :measure_name, :measure_id, :category attr_reader :score, :measure_name, :measure_id, :category
@ -30,7 +30,7 @@ class VarianceChartRowPresenter
def x_offset def x_offset
case zone.type case zone.type
when :ideal, :approval when :ideal, :approval
'60%' "60%"
else else
"#{((0.6 - bar_width_percentage) * 100).abs.round(2)}%" "#{((0.6 - bar_width_percentage) * 100).abs.round(2)}%"
end end
@ -57,9 +57,9 @@ class VarianceChartRowPresenter
def partial_data_sources def partial_data_sources
[].tap do |sources| [].tap do |sources|
sources << 'teacher survey results' if @measure.includes_teacher_survey_items? && !@meets_teacher_threshold sources << "teacher survey results" if @measure.includes_teacher_survey_items? && !@meets_teacher_threshold
sources << 'student survey results' if @measure.includes_student_survey_items? && !@meets_student_threshold sources << "student survey results" if @measure.includes_student_survey_items? && !@meets_student_threshold
sources << 'administrative data' if @measure.includes_admin_data_items? sources << "administrative data" if @measure.includes_admin_data_items?
end end
end end

@ -0,0 +1,18 @@
class ParentCategoryPresenter < CategoryPresenter
def harvey_scorecard_presenters(school:, academic_year:)
@category.scales.parent_scales.map do |scale|
measure = scale.measure
zones = Zones.new(
watch_low_benchmark: measure.watch_low_benchmark,
growth_low_benchmark: measure.growth_low_benchmark,
approval_low_benchmark: measure.approval_low_benchmark,
ideal_low_benchmark: measure.ideal_low_benchmark
)
Overview::ScorecardPresenter.new(construct: scale,
zones:,
score: scale.parent_score(school:, academic_year:),
id: scale.scale_id)
end
end
end

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class ParentScalePresenter < MeasurePresenter class ParentScalePresenter
attr_reader :scale, :academic_year, :school, :name, :description attr_reader :scale, :academic_year, :school, :name, :description, :id
def initialize(scale:, academic_year:, school:) def initialize(scale:, academic_year:, school:)
@scale = scale @scale = scale
@ -9,6 +9,7 @@ class ParentScalePresenter < MeasurePresenter
@school = school @school = school
@name = scale.name @name = scale.name
@description = scale.description @description = scale.description
@id = scale.scale_id
end end
def title def title

@ -16,10 +16,6 @@ class SubcategoryPresenter
GaugePresenter.new(zones:, score: average_score) GaugePresenter.new(zones:, score: average_score)
end end
def subcategory_card_presenter
SubcategoryCardPresenter.new(subcategory: @subcategory, zones:, score: average_score)
end
def average_score def average_score
@average_score ||= @subcategory.score(school: @school, academic_year: @academic_year) @average_score ||= @subcategory.score(school: @school, academic_year: @academic_year)
end end

@ -0,0 +1,7 @@
<div class="subcategory-card__benchmark-item">
<svg class="subcategory-card__circle" width="24" height="24" xmlns="http://www.w3.org/2000/svg" <%= "data-bs-toggle=popover" if harvey_scorecard.insufficient_data? %> data-bs-placement="top" data-bs-content="This subcategory is not displayed due to limited availability of school data and/or low survey response rates.">
<use class="harvey-ball harvey-ball--<%= harvey_scorecard.color %>" xlink:href="#<%= harvey_scorecard.harvey_ball_icon %>"></use>
</svg>
<%= link_to(harvey_scorecard.name, district_school_category_path( @district, @school, harvey_scorecard.category, {year: @academic_year.range, anchor: "#{harvey_scorecard.id}"})) %>
</div>

@ -1,22 +1,24 @@
<div class="mt-5 school-quality-frameworks"> <div class="d-flex justify-content-center">
<% category_presenters.each do |category_presenter| %> <div class="mt-5 <%= @page.framework_indicator_class %>">
<div class="text-center"> <% category_presenters.each do |category_presenter| %>
<i class="<%= category_presenter.icon_class %> <%= category_presenter.icon_color_class %> fa-2x"></i> <div class="text-center">
</div> <i class="<%= category_presenter.icon_class %> <%= category_presenter.icon_color_class %> fa-2x"></i>
<div class="text-center"> </div>
<h3 class="sub-header-3"> <div class="text-center">
<%= link_to [@district, @school, category_presenter, { year: @academic_year.range }] do %> <h3 class="sub-header-3">
<%= category_presenter.name %> <%= link_to [@district, @school, category_presenter, { year: @academic_year.range }] do %>
<% end %> <%= category_presenter.name %>
</h3> <% end %>
</div> </h3>
</div>
<p class="body-small text-center m-0"><%= category_presenter.short_description %></p> <p class="body-small text-center m-0"><%= category_presenter.short_description %></p>
<div class="subcategory-card"> <div class="subcategory-card">
<div class="subcategory-card__benchmark-list"> <div class="subcategory-card__benchmark-list">
<%= render partial: 'subcategory_card', collection: category_presenter.subcategories(academic_year: @academic_year, school: @school).map(&:subcategory_card_presenter) %> <%= render partial: 'harvey_scorecard', collection: category_presenter.harvey_scorecard_presenters(school: @school, academic_year: @academic_year) %>
</div>
</div> </div>
</div> <% end %>
<% end %> </div>
</div> </div>

@ -1,7 +1,10 @@
<div class="btn-group" role="group" aria-label="Basic radio toggle button group"> <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked> <%= link_to(district_school_overview_index_path(@district, @school, year: @academic_year.range, view: "student")) do %>
<label class="btn btn-outline-primary" for="btnradio1">Students & Teachers</label> <input type="radio" class="btn-check" name="student_and_teacher_btn" id="student_and_teacher_btn" autocomplete="off" <%= @page.view == "student" ? "checked" : "" %> >
<label class="btn btn-outline-primary" for="student_and_teacher_btn">Students & Teachers</label>
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off"> <% end %>
<label class="btn btn-outline-primary" for="btnradio2">Parents</label> <%= link_to(district_school_overview_index_path(@district, @school, year: @academic_year.range, view: "parent")) do %>
</div> <input type="radio" class="btn-check" name="parent_btn" id="parent_btn" autocomplete="off" <%= @page.view == "parent" ? "checked" : "" %> >
<label class="btn btn-outline-primary" for="parent_btn">Parents</label>
<% end %>
</div>

@ -1,7 +0,0 @@
<div class="subcategory-card__benchmark-item">
<svg class="subcategory-card__circle" width="24" height="24" xmlns="http://www.w3.org/2000/svg" <%= "data-bs-toggle=popover" if subcategory_card.insufficient_data? %> data-bs-placement="top" data-bs-content="This subcategory is not displayed due to limited availability of school data and/or low survey response rates.">
<use class="harvey-ball harvey-ball--<%= subcategory_card.color %>" xlink:href="#<%= subcategory_card.harvey_ball_icon %>"></use>
</svg>
<%= link_to(subcategory_card.name, district_school_category_path( @district, @school, subcategory_card.category, {year: @academic_year.range, anchor: "#{subcategory_card.subcategory_id}"})) %>
</div>

@ -18,10 +18,12 @@
</div> </div>
<%= render partial: "quality_framework_indicators", locals: { category_presenters: @category_presenters } %> <%= render partial: "quality_framework_indicators", locals: { category_presenters: @category_presenters } %>
<% if @page.show_response_rates %>
<div class="overall-response-rate-row"> <div class="overall-response-rate-row">
<%= render partial: "response_rate", locals: {response_rate_presenter: @student_response_rate_presenter} %> <%= render partial: "response_rate", locals: {response_rate_presenter: @student_response_rate_presenter} %>
<%= render partial: "response_rate", locals: {response_rate_presenter: @teacher_response_rate_presenter} %> <%= render partial: "response_rate", locals: {response_rate_presenter: @teacher_response_rate_presenter} %>
</div> </div>
<% end %>
</div> </div>
<div class="card"> <div class="card">

@ -0,0 +1,89 @@
require "rails_helper"
describe Overview::ScorecardPresenter do
let(:zones) do
Zones.new(
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5
)
end
let(:subcategory_card_presenter) do
subcategory = Subcategory.new(name: "A subcategory")
Overview::ScorecardPresenter.new(construct: subcategory, zones:, score:, id: subcategory.subcategory_id)
end
context "when the given score is in the Warning zone for the given scale" do
let(:score) { 1 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "warning-harvey-ball"
end
it "returns the color class of the zone" do
expect(subcategory_card_presenter.color).to eq "warning"
end
end
context "when the given score is in the Watch zone for the given scale" do
let(:score) { 2 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "watch-harvey-ball"
end
it "returns the color class of the zone" do
expect(subcategory_card_presenter.color).to eq "watch"
end
end
context "when the given score is in the Growth zone for the given scale" do
let(:score) { 3 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "growth-harvey-ball"
end
it "returns the color class of the zone" do
expect(subcategory_card_presenter.color).to eq "growth"
end
end
context "when the given score is in the Approval zone for the given scale" do
let(:score) { 4 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "approval-harvey-ball"
end
it "returns the color class of the zone" do
expect(subcategory_card_presenter.color).to eq "approval"
end
end
context "when the given score is in the Ideal zone for the given scale" do
let(:score) { 5 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "ideal-harvey-ball"
end
it "returns the color class of the zone" do
expect(subcategory_card_presenter.color).to eq "ideal"
end
end
context "when the given score is invalid for the given scale" do
let(:score) { 0 }
it "returns the icon that represents the zone" do
expect(subcategory_card_presenter.harvey_ball_icon).to eq "insufficient_data-harvey-ball"
end
it "reports that there is insufficient data" do
expect(subcategory_card_presenter.insufficient_data?).to be true
end
end
end

@ -1,86 +0,0 @@
require 'rails_helper'
describe SubcategoryCardPresenter do
let(:zones) do
Zones.new(
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5
)
end
let(:subcategory_card_presenter) { SubcategoryCardPresenter.new(subcategory: Subcategory.new(name: 'A subcategory'), zones:, score:) }
context 'when the given score is in the Warning zone for the given scale' do
let(:score) { 1 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'warning-harvey-ball'
end
it 'returns the color class of the zone' do
expect(subcategory_card_presenter.color).to eq 'warning'
end
end
context 'when the given score is in the Watch zone for the given scale' do
let(:score) { 2 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'watch-harvey-ball'
end
it 'returns the color class of the zone' do
expect(subcategory_card_presenter.color).to eq 'watch'
end
end
context 'when the given score is in the Growth zone for the given scale' do
let(:score) { 3 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'growth-harvey-ball'
end
it 'returns the color class of the zone' do
expect(subcategory_card_presenter.color).to eq 'growth'
end
end
context 'when the given score is in the Approval zone for the given scale' do
let(:score) { 4 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'approval-harvey-ball'
end
it 'returns the color class of the zone' do
expect(subcategory_card_presenter.color).to eq 'approval'
end
end
context 'when the given score is in the Ideal zone for the given scale' do
let(:score) { 5 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'ideal-harvey-ball'
end
it 'returns the color class of the zone' do
expect(subcategory_card_presenter.color).to eq 'ideal'
end
end
context 'when the given score is invalid for the given scale' do
let(:score) { 0 }
it 'returns the icon that represents the zone' do
expect(subcategory_card_presenter.harvey_ball_icon).to eq 'insufficient_data-harvey-ball'
end
it 'reports that there is insufficient data' do
expect(subcategory_card_presenter.insufficient_data?).to be true
end
end
end

@ -85,16 +85,6 @@ describe SubcategoryPresenter do
expect(subcategory_presenter.measure_presenters.count).to eq subcategory.measures.count expect(subcategory_presenter.measure_presenters.count).to eq subcategory.measures.count
end end
context "When there are no measures populated with student or teacher surveys" do
let(:empty_subcategory) { create :subcategory }
let(:empty_subcategory_presenter) do
SubcategoryPresenter.new(subcategory: empty_subcategory, academic_year:, school:)
end
it "should make a subcategory presenter return insufficient data" do
expect(empty_subcategory_presenter.subcategory_card_presenter.insufficient_data?).to eq true
end
end
def create_survey_item_responses_for_different_years_and_schools(survey_item) def create_survey_item_responses_for_different_years_and_schools(survey_item)
create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item:, create_list(:survey_item_response, SurveyItemResponse::TEACHER_RESPONSE_THRESHOLD, survey_item:,
school: worst_school, likert_score: 1) school: worst_school, likert_score: 1)

@ -1,6 +1,6 @@
require 'rails_helper' require "rails_helper"
describe VarianceChartRowPresenter do describe Overview::VarianceChartRowPresenter do
let(:watch_low_benchmark) { 2.9 } let(:watch_low_benchmark) { 2.9 }
let(:growth_low_benchmark) { 3.1 } let(:growth_low_benchmark) { 3.1 }
let(:approval_low_benchmark) { 3.6 } let(:approval_low_benchmark) { 3.6 }
@ -9,7 +9,7 @@ describe VarianceChartRowPresenter do
let(:measure) do let(:measure) do
measure = create( measure = create(
:measure, :measure,
name: 'Some Title' name: "Some Title"
) )
scale = create(:scale, measure:) scale = create(:scale, measure:)
@ -25,210 +25,210 @@ describe VarianceChartRowPresenter do
let(:measure_without_admin_data_items) do let(:measure_without_admin_data_items) do
create( create(
:measure, :measure,
name: 'Some Title' name: "Some Title"
) )
end end
let(:presenter) do let(:presenter) do
VarianceChartRowPresenter.new measure:, score: Overview::VarianceChartRowPresenter.new measure:, score:
end end
shared_examples_for 'measure_name' do shared_examples_for "measure_name" do
it 'returns the measure name' do it "returns the measure name" do
expect(presenter.measure_name).to eq 'Some Title' expect(presenter.measure_name).to eq "Some Title"
end end
end end
context 'when the score is in the Ideal zone' do context "when the score is in the Ideal zone" do
let(:score) { Score.new(average: 4.4, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: 4.4, meets_teacher_threshold: true, meets_student_threshold: true) }
it_behaves_like 'measure_name' it_behaves_like "measure_name"
it 'returns the correct color' do it "returns the correct color" do
expect(presenter.bar_color).to eq 'fill-ideal' expect(presenter.bar_color).to eq "fill-ideal"
end end
it 'returns a bar width equal to the approval zone width plus the proportionate ideal zone width' do it "returns a bar width equal to the approval zone width plus the proportionate ideal zone width" do
expect(presenter.bar_width).to eq '30.0%' expect(presenter.bar_width).to eq "30.0%"
end end
it 'returns an x-offset of 60%' do it "returns an x-offset of 60%" do
expect(presenter.x_offset).to eq '60%' expect(presenter.x_offset).to eq "60%"
end end
end end
context 'when the score is in the Approval zone' do context "when the score is in the Approval zone" do
let(:score) { Score.new(average: 3.7, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: 3.7, meets_teacher_threshold: true, meets_student_threshold: true) }
it_behaves_like 'measure_name' it_behaves_like "measure_name"
it 'returns the correct color' do it "returns the correct color" do
expect(presenter.bar_color).to eq 'fill-approval' expect(presenter.bar_color).to eq "fill-approval"
end end
it 'returns a bar width equal to the proportionate approval zone width' do it "returns a bar width equal to the proportionate approval zone width" do
expect(presenter.bar_width).to eq '10.0%' expect(presenter.bar_width).to eq "10.0%"
end end
it 'returns an x-offset of 60%' do it "returns an x-offset of 60%" do
expect(presenter.x_offset).to eq '60%' expect(presenter.x_offset).to eq "60%"
end end
end end
context 'when the score is in the Growth zone' do context "when the score is in the Growth zone" do
let(:score) { Score.new(average: 3.2, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: 3.2, meets_teacher_threshold: true, meets_student_threshold: true) }
it_behaves_like 'measure_name' it_behaves_like "measure_name"
it 'returns the correct color' do it "returns the correct color" do
expect(presenter.bar_color).to eq 'fill-growth' expect(presenter.bar_color).to eq "fill-growth"
end end
it 'returns a bar width equal to the proportionate growth zone width' do it "returns a bar width equal to the proportionate growth zone width" do
expect(presenter.bar_width).to eq '16.0%' expect(presenter.bar_width).to eq "16.0%"
end end
context 'in order to achieve the visual effect' do context "in order to achieve the visual effect" do
it 'returns an x-offset equal to 60% minus the bar width' do it "returns an x-offset equal to 60% minus the bar width" do
expect(presenter.x_offset).to eq '44.0%' expect(presenter.x_offset).to eq "44.0%"
end end
end end
end end
context 'when the score is in the Watch zone' do context "when the score is in the Watch zone" do
let(:score) { Score.new(average: 2.9, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: 2.9, meets_teacher_threshold: true, meets_student_threshold: true) }
it_behaves_like 'measure_name' it_behaves_like "measure_name"
it 'returns the correct color' do it "returns the correct color" do
expect(presenter.bar_color).to eq 'fill-watch' expect(presenter.bar_color).to eq "fill-watch"
end end
it 'returns a bar width equal to the proportionate watch zone width plus the growth zone width' do it "returns a bar width equal to the proportionate watch zone width plus the growth zone width" do
expect(presenter.bar_width).to eq '40.0%' expect(presenter.bar_width).to eq "40.0%"
end end
context 'in order to achieve the visual effect' do context "in order to achieve the visual effect" do
it 'returns an x-offset equal to 60% minus the bar width' do it "returns an x-offset equal to 60% minus the bar width" do
expect(presenter.x_offset).to eq '20.0%' expect(presenter.x_offset).to eq "20.0%"
end end
end end
end end
context 'when the score is in the Warning zone' do context "when the score is in the Warning zone" do
let(:score) { Score.new(average: 1.0, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: 1.0, meets_teacher_threshold: true, meets_student_threshold: true) }
it_behaves_like 'measure_name' it_behaves_like "measure_name"
it 'returns the correct color' do it "returns the correct color" do
expect(presenter.bar_color).to eq 'fill-warning' expect(presenter.bar_color).to eq "fill-warning"
end end
it 'returns a bar width equal to the proportionate warning zone width plus the watch & growth zone widths' do it "returns a bar width equal to the proportionate warning zone width plus the watch & growth zone widths" do
expect(presenter.bar_width).to eq '60.0%' expect(presenter.bar_width).to eq "60.0%"
end end
context 'in order to achieve the visual effect' do context "in order to achieve the visual effect" do
it 'returns an x-offset equal to 60% minus the bar width' do it "returns an x-offset equal to 60% minus the bar width" do
expect(presenter.x_offset).to eq '0.0%' expect(presenter.x_offset).to eq "0.0%"
end end
end end
end end
context 'when a measure does not contain admin data items' do context "when a measure does not contain admin data items" do
let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: false) } let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: false) }
it 'it does not show a partial data indicator' do it "it does not show a partial data indicator" do
presenter_without_admin_data = VarianceChartRowPresenter.new measure: measure_without_admin_data_items, presenter_without_admin_data = Overview::VarianceChartRowPresenter.new(measure: measure_without_admin_data_items,
score: score score:)
expect(presenter_without_admin_data.show_partial_data_indicator?).to be false expect(presenter_without_admin_data.show_partial_data_indicator?).to be false
end end
end end
context 'when a measure contains admin data items' do context "when a measure contains admin data items" do
before :each do before :each do
end end
let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: false) } let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: false) }
it 'shows a partial data indicator' do it "shows a partial data indicator" do
measure_with_admin_data = create( measure_with_admin_data = create(
:measure, :measure,
name: 'Some Title' name: "Some Title"
) )
scale_with_admin_data = create(:scale, measure: measure_with_admin_data) scale_with_admin_data = create(:scale, measure: measure_with_admin_data)
create :admin_data_item, create(:admin_data_item,
scale: scale_with_admin_data, scale: scale_with_admin_data,
watch_low_benchmark: watch_low_benchmark, watch_low_benchmark:,
growth_low_benchmark: growth_low_benchmark, growth_low_benchmark:,
approval_low_benchmark: approval_low_benchmark, approval_low_benchmark:,
ideal_low_benchmark: ideal_low_benchmark ideal_low_benchmark:)
admin_data_presenter = VarianceChartRowPresenter.new measure: measure_with_admin_data, admin_data_presenter = Overview::VarianceChartRowPresenter.new measure: measure_with_admin_data,
score: Score.new( score: Score.new(
average: 3.7, meets_teacher_threshold: true, meets_student_threshold: true average: 3.7, meets_teacher_threshold: true, meets_student_threshold: true
) )
expect(admin_data_presenter.show_partial_data_indicator?).to be true expect(admin_data_presenter.show_partial_data_indicator?).to be true
expect(admin_data_presenter.partial_data_sources).to eq ['administrative data'] expect(admin_data_presenter.partial_data_sources).to eq ["administrative data"]
end end
end end
context 'when a measure contains teacher survey items' do context "when a measure contains teacher survey items" do
before :each do before :each do
scale = create(:scale, measure:) scale = create(:scale, measure:)
create :teacher_survey_item, scale: create :teacher_survey_item, scale:
end end
context 'when there are insufficient teacher survey item responses' do context "when there are insufficient teacher survey item responses" do
let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: true) } let(:score) { Score.new(average: nil, meets_teacher_threshold: false, meets_student_threshold: true) }
it 'shows a partial data indicator' do it "shows a partial data indicator" do
expect(presenter.show_partial_data_indicator?).to be true expect(presenter.show_partial_data_indicator?).to be true
expect(presenter.partial_data_sources).to eq ['teacher survey results'] expect(presenter.partial_data_sources).to eq ["teacher survey results"]
end end
end end
context 'when there are sufficient teacher survey item responses' do context "when there are sufficient teacher survey item responses" do
let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: true) }
it 'does not show a partial data indicator' do it "does not show a partial data indicator" do
expect(presenter.show_partial_data_indicator?).to be false expect(presenter.show_partial_data_indicator?).to be false
end end
end end
end end
context 'when a measure contains student survey items' do context "when a measure contains student survey items" do
before :each do before :each do
scale = create(:scale, measure:) scale = create(:scale, measure:)
create :student_survey_item, scale: create :student_survey_item, scale:
end end
context 'when there are insufficient student survey item responses' do context "when there are insufficient student survey item responses" do
let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: false) } let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: false) }
it 'shows a partial data indicator' do it "shows a partial data indicator" do
expect(presenter.show_partial_data_indicator?).to be true expect(presenter.show_partial_data_indicator?).to be true
expect(presenter.partial_data_sources).to eq ['student survey results'] expect(presenter.partial_data_sources).to eq ["student survey results"]
end end
context 'where there are also admin data items' do context "where there are also admin data items" do
before :each do before :each do
scale = create(:scale, measure:) scale = create(:scale, measure:)
create :admin_data_item, scale: create :admin_data_item, scale:
end end
it 'returns the sources for partial results of administrative data and student survey results' do it "returns the sources for partial results of administrative data and student survey results" do
expect(presenter.partial_data_sources).to eq ['student survey results', 'administrative data'] expect(presenter.partial_data_sources).to eq ["student survey results", "administrative data"]
end end
end end
end end
context 'When there are sufficient student survey item responses' do context "When there are sufficient student survey item responses" do
let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: true) } let(:score) { Score.new(average: nil, meets_teacher_threshold: true, meets_student_threshold: true) }
it 'does not show a partial data indicator' do it "does not show a partial data indicator" do
expect(presenter.show_partial_data_indicator?).to be false expect(presenter.show_partial_data_indicator?).to be false
end end
end end
end end
context 'sorting scores' do context "sorting scores" do
it 'selects a longer bar before a shorter bar for measures in the approval/ideal zones' do it "selects a longer bar before a shorter bar for measures in the approval/ideal zones" do
scale_with_student_survey_items = create(:scale, measure:) scale_with_student_survey_items = create(:scale, measure:)
create(:student_survey_item, create(:student_survey_item,
scale: scale_with_student_survey_items, scale: scale_with_student_survey_items,
@ -236,15 +236,27 @@ describe VarianceChartRowPresenter do
growth_low_benchmark:, growth_low_benchmark:,
approval_low_benchmark:, approval_low_benchmark:,
ideal_low_benchmark:) ideal_low_benchmark:)
approval_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(average: 3.7, meets_teacher_threshold: true,meets_student_threshold: true) approval_presenter = Overview::VarianceChartRowPresenter.new measure:,
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(average: 4.4, meets_teacher_threshold: true, meets_student_threshold: true) score: Score.new(
average: 3.7, meets_teacher_threshold: true, meets_student_threshold: true
)
ideal_presenter = Overview::VarianceChartRowPresenter.new measure:,
score: Score.new(
average: 4.4, meets_teacher_threshold: true, meets_student_threshold: true
)
expect(ideal_presenter <=> approval_presenter).to be < 0 expect(ideal_presenter <=> approval_presenter).to be < 0
expect([approval_presenter, ideal_presenter].sort).to eq [ideal_presenter, approval_presenter] expect([approval_presenter, ideal_presenter].sort).to eq [ideal_presenter, approval_presenter]
end end
it 'selects a warning bar below a ideal bar' do it "selects a warning bar below a ideal bar" do
warning_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(average: 1.0, meets_teacher_threshold: true, meets_student_threshold: true) warning_presenter = Overview::VarianceChartRowPresenter.new measure:,
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(average: 5.0, meets_teacher_threshold: true, meets_student_threshold: true) score: Score.new(
average: 1.0, meets_teacher_threshold: true, meets_student_threshold: true
)
ideal_presenter = Overview::VarianceChartRowPresenter.new measure:,
score: Score.new(
average: 5.0, meets_teacher_threshold: true, meets_student_threshold: true
)
expect(warning_presenter <=> ideal_presenter).to be > 0 expect(warning_presenter <=> ideal_presenter).to be > 0
expect([warning_presenter, ideal_presenter].sort).to eq [ideal_presenter, warning_presenter] expect([warning_presenter, ideal_presenter].sort).to eq [ideal_presenter, warning_presenter]
end end

@ -40,31 +40,33 @@ describe "overview/index" do
measure measure
end end
before :each do
assign :category_presenters, []
assign :variance_chart_row_presenters, variance_chart_row_presenters
@academic_year = create(:academic_year)
assign :academic_years, [@academic_year]
@district = create(:district)
@school = create(:school)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school,
academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school,
academic_year: @academic_year)
Respondent.create!(school: @school, academic_year: @academic_year, total_students: 40, total_teachers: 40)
ResponseRate.create!(subcategory: Subcategory.first, school: @school, academic_year: @academic_year,
student_response_rate: 100, teacher_response_rate: 100, meets_student_threshold: true, meets_teacher_threshold: true)
render
end
context "when some presenters have a nil score" do context "when some presenters have a nil score" do
before :each do
assign :category_presenters, []
assign :variance_chart_row_presenters, variance_chart_row_presenters
@academic_year = create(:academic_year)
assign :academic_years, [@academic_year]
@district = create(:district)
@school = create(:school)
assign :page,
Overview::OverviewPresenter.new(params: { view: "student" }, school: @school,
academic_year: @academic_year)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school,
academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school,
academic_year: @academic_year)
Respondent.create!(school: @school, academic_year: @academic_year, total_students: 40, total_teachers: 40)
ResponseRate.create!(subcategory: Subcategory.first, school: @school, academic_year: @academic_year,
student_response_rate: 100, teacher_response_rate: 100, meets_student_threshold: true, meets_teacher_threshold: true)
render
end
let(:variance_chart_row_presenters) do let(:variance_chart_row_presenters) do
[ [
VarianceChartRowPresenter.new(measure: support_for_teaching, score: Score.new), Overview::VarianceChartRowPresenter.new(measure: support_for_teaching, score: Score.new),
VarianceChartRowPresenter.new(measure: effective_leadership, score: Score.new(average: rand)), Overview::VarianceChartRowPresenter.new(measure: effective_leadership, score: Score.new(average: rand)),
VarianceChartRowPresenter.new(measure: professional_qualifications, score: Score.new) Overview::VarianceChartRowPresenter.new(measure: professional_qualifications, score: Score.new)
] ]
end end
@ -84,6 +86,27 @@ describe "overview/index" do
end end
context "when all the presenters have a non-nil score" do context "when all the presenters have a non-nil score" do
before :each do
assign :category_presenters, []
assign :variance_chart_row_presenters, variance_chart_row_presenters
@academic_year = create(:academic_year)
assign :academic_years, [@academic_year]
@district = create(:district)
@school = create(:school)
assign :page,
Overview::OverviewPresenter.new(params: { view: "student" }, school: @school,
academic_year: @academic_year)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school,
academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school,
academic_year: @academic_year)
Respondent.create!(school: @school, academic_year: @academic_year, total_students: 40, total_teachers: 40)
ResponseRate.create!(subcategory: Subcategory.first, school: @school, academic_year: @academic_year,
student_response_rate: 100, teacher_response_rate: 100, meets_student_threshold: true, meets_teacher_threshold: true)
render
end
let(:variance_chart_row_presenters) do let(:variance_chart_row_presenters) do
measure = create(:measure, name: "Display Me", measure_id: "display-me") measure = create(:measure, name: "Display Me", measure_id: "display-me")
scale = create(:scale, measure:) scale = create(:scale, measure:)
@ -94,8 +117,8 @@ describe "overview/index" do
approval_low_benchmark: 3.5, approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5) ideal_low_benchmark: 4.5)
[ [
VarianceChartRowPresenter.new(measure:, Overview::VarianceChartRowPresenter.new(measure:,
score: Score.new(average: rand)) score: Score.new(average: rand))
] ]
end end
@ -113,4 +136,88 @@ describe "overview/index" do
expect(displayed_variance_labels.first.inner_text).to include "Display Me" expect(displayed_variance_labels.first.inner_text).to include "Display Me"
end end
end end
context "when the default view is shown" do
let(:variance_chart_row_presenters) do
measure = create(:measure, name: "Display Me", measure_id: "display-me")
scale = create(:scale, measure:)
create(:student_survey_item,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
[
Overview::VarianceChartRowPresenter.new(measure:,
score: Score.new(average: rand))
]
end
before :each do
assign :category_presenters, []
assign :variance_chart_row_presenters, variance_chart_row_presenters
@academic_year = create(:academic_year)
assign :academic_years, [@academic_year]
@district = create(:district)
@school = create(:school)
assign :page,
Overview::OverviewPresenter.new(params: { view: "student" }, school: @school,
academic_year: @academic_year)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school,
academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school,
academic_year: @academic_year)
Respondent.create!(school: @school, academic_year: @academic_year, total_students: 40, total_teachers: 40)
ResponseRate.create!(subcategory: Subcategory.first, school: @school, academic_year: @academic_year,
student_response_rate: 100, teacher_response_rate: 100, meets_student_threshold: true, meets_teacher_threshold: true)
render
end
it "shows the view with the students & teachers button active" do
expect(subject.css("input[id='student_and_teacher_btn'][checked='checked']").count).to eq 1
end
end
context "when the parent view is shown" do
let(:variance_chart_row_presenters) do
measure = create(:measure, name: "Display Me", measure_id: "display-me")
scale = create(:scale, measure:)
create(:student_survey_item,
scale:,
watch_low_benchmark: 1.5,
growth_low_benchmark: 2.5,
approval_low_benchmark: 3.5,
ideal_low_benchmark: 4.5)
[
Overview::VarianceChartRowPresenter.new(measure:,
score: Score.new(average: rand))
]
end
before :each do
assign :category_presenters, []
assign :variance_chart_row_presenters, variance_chart_row_presenters
@academic_year = create(:academic_year)
assign :academic_years, [@academic_year]
@district = create(:district)
@school = create(:school)
assign :page,
Overview::OverviewPresenter.new(params: { view: "parent" }, school: @school,
academic_year: @academic_year)
@student_response_rate_presenter = ResponseRatePresenter.new(focus: :student, school: @school,
academic_year: @academic_year)
@teacher_response_rate_presenter = ResponseRatePresenter.new(focus: :teacher, school: @school,
academic_year: @academic_year)
Respondent.create!(school: @school, academic_year: @academic_year, total_students: 40, total_teachers: 40)
ResponseRate.create!(subcategory: Subcategory.first, school: @school, academic_year: @academic_year,
student_response_rate: 100, teacher_response_rate: 100, meets_student_threshold: true, meets_teacher_threshold: true)
render
end
it "shows the view with the students & teachers button active" do
expect(subject.css("input[id='parent_btn'][checked='checked']").count).to eq 1
end
end
end end

@ -27,8 +27,8 @@ describe "overview/_variance_chart.html.erb" do
before :each do before :each do
presenters = [ presenters = [
VarianceChartRowPresenter.new(measure: lower_scoring_measure, score: Score.new(average: 1)), Overview::VarianceChartRowPresenter.new(measure: lower_scoring_measure, score: Score.new(average: 1)),
VarianceChartRowPresenter.new(measure: higher_scoring_measure, score: Score.new(average: 5)) Overview::VarianceChartRowPresenter.new(measure: higher_scoring_measure, score: Score.new(average: 5))
] ]
render partial: "variance_chart", locals: { presenters: } render partial: "variance_chart", locals: { presenters: }
@ -53,8 +53,8 @@ describe "overview/_variance_chart.html.erb" do
measure_lacking_score = create(:measure) measure_lacking_score = create(:measure)
another_measure_lacking_score = create(:measure) another_measure_lacking_score = create(:measure)
presenters = [ presenters = [
VarianceChartRowPresenter.new(measure: measure_lacking_score, score: Score.new(average: nil)), Overview::VarianceChartRowPresenter.new(measure: measure_lacking_score, score: Score.new(average: nil)),
VarianceChartRowPresenter.new(measure: another_measure_lacking_score, score: Score.new(average: nil)) Overview::VarianceChartRowPresenter.new(measure: another_measure_lacking_score, score: Score.new(average: nil))
] ]
render partial: "variance_chart", locals: { presenters: } render partial: "variance_chart", locals: { presenters: }

Loading…
Cancel
Save