mirror of
https://github.com/edcommonwealth/sqm-dashboards.git
synced 2026-03-07 13:38:18 -08:00
we have a gauge! next up, let's style the page
This commit is contained in:
parent
5d6c1000f4
commit
25578a896f
29 changed files with 614 additions and 112 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
trim_trailing_whitespace = true
|
||||
1
Gemfile
1
Gemfile
|
|
@ -54,6 +54,7 @@ gem 'activerecord-import'
|
|||
group :development, :test do
|
||||
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
||||
gem 'byebug', platform: :mri
|
||||
gem 'factory_bot_rails'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
|
|
|||
|
|
@ -78,6 +78,11 @@ GEM
|
|||
diff-lcs (1.4.4)
|
||||
erubi (1.10.0)
|
||||
execjs (2.8.1)
|
||||
factory_bot (6.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.2.0)
|
||||
factory_bot (~> 6.2.0)
|
||||
railties (>= 5.0.0)
|
||||
ffi (1.15.4)
|
||||
friendly_id (5.1.0)
|
||||
activerecord (>= 4.0.0)
|
||||
|
|
@ -258,6 +263,7 @@ DEPENDENCIES
|
|||
capybara
|
||||
database_cleaner
|
||||
devise
|
||||
factory_bot_rails
|
||||
friendly_id (~> 5.1.0)
|
||||
haml
|
||||
jbuilder (~> 2.5)
|
||||
|
|
|
|||
|
|
@ -17,10 +17,14 @@ $growth: #E0BA9A;
|
|||
$approval: #D0DD86;
|
||||
$ideal: #C0FF73;
|
||||
|
||||
.red {
|
||||
.color-red {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.color-black {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.bg-beige {
|
||||
background-color: $beige;
|
||||
}
|
||||
|
|
@ -29,6 +33,10 @@ $ideal: #C0FF73;
|
|||
background-color: $white;
|
||||
}
|
||||
|
||||
.fill-black {
|
||||
fill: $black;
|
||||
}
|
||||
|
||||
.fill-warning {
|
||||
fill: $warning;
|
||||
}
|
||||
|
|
@ -48,3 +56,15 @@ $ideal: #C0FF73;
|
|||
.fill-ideal {
|
||||
fill: $ideal;
|
||||
}
|
||||
|
||||
.stroke-black {
|
||||
stroke: $black;
|
||||
}
|
||||
|
||||
.stroke-gray-1 {
|
||||
stroke: $gray-1;
|
||||
}
|
||||
|
||||
.stroke-gray-2 {
|
||||
stroke: $gray-2;
|
||||
}
|
||||
|
|
|
|||
23
app/controllers/browse_controller.rb
Normal file
23
app/controllers/browse_controller.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
class BrowseController < ApplicationController
|
||||
def show
|
||||
@category = CategoryPresenter.new(
|
||||
category: SqmCategory.find_by_name('Teachers & Leadership'),
|
||||
academic_year: academic_year,
|
||||
school: school,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def school
|
||||
@school ||= School.find_by_slug school_slug
|
||||
end
|
||||
|
||||
def school_slug
|
||||
params[:school_id]
|
||||
end
|
||||
|
||||
def academic_year
|
||||
@academic_year ||= AcademicYear.find_by_range params[:year]
|
||||
end
|
||||
end
|
||||
|
|
@ -1,44 +1,6 @@
|
|||
class Measure < ActiveRecord::Base
|
||||
belongs_to :subcategory
|
||||
has_many :survey_items
|
||||
|
||||
Zone = Struct.new(:low_benchmark, :high_benchmark, :type) do
|
||||
def includes_score?(score)
|
||||
score > low_benchmark and score < high_benchmark
|
||||
end
|
||||
end
|
||||
|
||||
def warning_zone
|
||||
Zone.new(1, watch_low_benchmark, :warning)
|
||||
end
|
||||
|
||||
def watch_zone
|
||||
Zone.new(watch_low_benchmark, growth_low_benchmark, :watch)
|
||||
end
|
||||
|
||||
def growth_zone
|
||||
Zone.new(growth_low_benchmark, approval_low_benchmark, :growth)
|
||||
end
|
||||
|
||||
def approval_zone
|
||||
Zone.new(approval_low_benchmark, ideal_low_benchmark, :approval)
|
||||
end
|
||||
|
||||
def ideal_zone
|
||||
Zone.new(ideal_low_benchmark, 5.0, :ideal)
|
||||
end
|
||||
|
||||
def zone_for_score(score)
|
||||
case score
|
||||
when ideal_zone.low_benchmark..ideal_zone.high_benchmark
|
||||
ideal_zone
|
||||
when approval_zone.low_benchmark..approval_zone.high_benchmark
|
||||
approval_zone
|
||||
when growth_zone.low_benchmark..growth_zone.high_benchmark
|
||||
growth_zone
|
||||
when watch_zone.low_benchmark..watch_zone.high_benchmark
|
||||
watch_zone
|
||||
else
|
||||
warning_zone
|
||||
end
|
||||
end
|
||||
has_many :survey_item_responses, through: :survey_items
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
class SqmCategory < ActiveRecord::Base
|
||||
|
||||
has_many :subcategories
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
class Subcategory < ActiveRecord::Base
|
||||
belongs_to :sqm_category
|
||||
|
||||
has_many :measures
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
class SurveyItem < ActiveRecord::Base
|
||||
belongs_to :measure
|
||||
has_many :survey_item_responses
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,4 +2,7 @@ class SurveyItemResponse < ActiveRecord::Base
|
|||
belongs_to :academic_year
|
||||
belongs_to :school
|
||||
belongs_to :survey_item
|
||||
|
||||
scope :for_measures, ->(measures) { joins(:survey_item).where('survey_items.measure_id': measures.map(&:id)) }
|
||||
scope :for_measure, ->(measure) { joins(:survey_item).where('survey_items.measure_id': measure.id) }
|
||||
end
|
||||
|
|
|
|||
21
app/presenters/category_presenter.rb
Normal file
21
app/presenters/category_presenter.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
class CategoryPresenter
|
||||
def initialize(category:, academic_year:, school:)
|
||||
@category = category
|
||||
@academic_year = academic_year
|
||||
@school = school
|
||||
end
|
||||
|
||||
def name
|
||||
@category.name
|
||||
end
|
||||
|
||||
def subcategories
|
||||
@category.subcategories.map do |subcategory|
|
||||
SubcategoryPresenter.new(
|
||||
subcategory: subcategory,
|
||||
academic_year: @academic_year,
|
||||
school: @school,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
34
app/presenters/gauge_presenter.rb
Normal file
34
app/presenters/gauge_presenter.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
class GaugePresenter
|
||||
def initialize(scale:, score:)
|
||||
@scale = scale
|
||||
@score = score
|
||||
end
|
||||
|
||||
def title
|
||||
zone.type.to_s.capitalize
|
||||
end
|
||||
|
||||
def color_class
|
||||
"fill-#{zone.type}"
|
||||
end
|
||||
|
||||
def score_percentage
|
||||
percentage_for @score
|
||||
end
|
||||
|
||||
def key_benchmark_percentage
|
||||
percentage_for @scale.approval_zone.low_benchmark
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def zone
|
||||
@scale.zone_for_score(@score)
|
||||
end
|
||||
|
||||
def percentage_for(number)
|
||||
scale_minimum = @scale.warning_zone.low_benchmark
|
||||
scale_maximum = @scale.ideal_zone.high_benchmark
|
||||
(number - scale_minimum) / (scale_maximum - scale_minimum)
|
||||
end
|
||||
end
|
||||
|
|
@ -26,7 +26,7 @@ class MeasureGraphRowPresenter
|
|||
when :ideal, :approval
|
||||
"50%"
|
||||
else
|
||||
"#{((0.5 - bar_width_percentage) * 100).round(2)}%"
|
||||
"#{((0.5 - bar_width_percentage) * 100).abs.round(2)}%"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -66,6 +66,12 @@ class MeasureGraphRowPresenter
|
|||
end
|
||||
|
||||
def zone
|
||||
@measure.zone_for_score(@score)
|
||||
scale = Scale.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,
|
||||
)
|
||||
scale.zone_for_score(@score)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
45
app/presenters/scale.rb
Normal file
45
app/presenters/scale.rb
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
class Scale
|
||||
def initialize(watch_low_benchmark:, growth_low_benchmark:, approval_low_benchmark:, ideal_low_benchmark:)
|
||||
@watch_low_benchmark = watch_low_benchmark
|
||||
@growth_low_benchmark = growth_low_benchmark
|
||||
@approval_low_benchmark = approval_low_benchmark
|
||||
@ideal_low_benchmark = ideal_low_benchmark
|
||||
end
|
||||
|
||||
Zone = Struct.new(:low_benchmark, :high_benchmark, :type)
|
||||
|
||||
def warning_zone
|
||||
Zone.new(1, @watch_low_benchmark, :warning)
|
||||
end
|
||||
|
||||
def watch_zone
|
||||
Zone.new(@watch_low_benchmark, @growth_low_benchmark, :watch)
|
||||
end
|
||||
|
||||
def growth_zone
|
||||
Zone.new(@growth_low_benchmark, @approval_low_benchmark, :growth)
|
||||
end
|
||||
|
||||
def approval_zone
|
||||
Zone.new(@approval_low_benchmark, @ideal_low_benchmark, :approval)
|
||||
end
|
||||
|
||||
def ideal_zone
|
||||
Zone.new(@ideal_low_benchmark, 5.0, :ideal)
|
||||
end
|
||||
|
||||
def zone_for_score(score)
|
||||
case score
|
||||
when ideal_zone.low_benchmark..ideal_zone.high_benchmark
|
||||
ideal_zone
|
||||
when approval_zone.low_benchmark..approval_zone.high_benchmark
|
||||
approval_zone
|
||||
when growth_zone.low_benchmark..growth_zone.high_benchmark
|
||||
growth_zone
|
||||
when watch_zone.low_benchmark..watch_zone.high_benchmark
|
||||
watch_zone
|
||||
else
|
||||
warning_zone
|
||||
end
|
||||
end
|
||||
end
|
||||
34
app/presenters/subcategory_presenter.rb
Normal file
34
app/presenters/subcategory_presenter.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
class SubcategoryPresenter
|
||||
def initialize(subcategory:, academic_year:, school:)
|
||||
@subcategory = subcategory
|
||||
@academic_year = academic_year
|
||||
@school = school
|
||||
end
|
||||
|
||||
def name
|
||||
@subcategory.name
|
||||
end
|
||||
|
||||
def gauge_presenter
|
||||
average_score = SurveyItemResponse.for_measures(measures)
|
||||
.where(academic_year: @academic_year, school: @school)
|
||||
.average(:likert_score)
|
||||
|
||||
GaugePresenter.new(scale: scale, score: average_score)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scale
|
||||
Scale.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,
|
||||
)
|
||||
end
|
||||
|
||||
def measures
|
||||
@measures ||= @subcategory.measures
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
# TODO inline me pls
|
||||
class SurveyResponseAggregator
|
||||
# Returns an average score for all SurveyItemResponses for the given AcademicYear, School, and Measure
|
||||
def self.score(academic_year:, school:, measure:)
|
||||
SurveyItemResponse
|
||||
SurveyItemResponse.for_measure(measure)
|
||||
.where(academic_year: academic_year, school: school)
|
||||
.joins(:survey_item).where('survey_items.measure_id': measure.id)
|
||||
.map { |survey_item_response| survey_item_response.likert_score }
|
||||
.average
|
||||
.average(:likert_score)
|
||||
end
|
||||
|
||||
# Returns an array of SurveyItemResponses for the given AcademicYear, School, and Measure
|
||||
|
|
|
|||
60
app/views/browse/_gauge_graph.html.erb
Normal file
60
app/views/browse/_gauge_graph.html.erb
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<% outer_radius = 100 %>
|
||||
<% inner_radius = 50 %>
|
||||
<% stroke_width = 1 %>
|
||||
<% key_benchmark_indicator_height = 10 %>
|
||||
<% scale = -Math::PI %>
|
||||
|
||||
<svg
|
||||
viewBox="<%= -(outer_radius + stroke_width) %> <%= -(outer_radius + stroke_width) %> <%= 2*(outer_radius + stroke_width) %> <%= outer_radius + 2*stroke_width + key_benchmark_indicator_height %>"
|
||||
width="400"
|
||||
height="200"
|
||||
>
|
||||
<path
|
||||
class="gauge-fill <%= gauge.color_class %>"
|
||||
d="M <%= -outer_radius %> <%= key_benchmark_indicator_height %>
|
||||
A <%= outer_radius %> <%= outer_radius %> 0 0 1 <%= outer_radius * Math.cos((1 - gauge.score_percentage) * scale) %> <%= outer_radius * Math.sin((1 - gauge.score_percentage) * scale) + key_benchmark_indicator_height %>
|
||||
L <%= inner_radius * Math.cos((1 - gauge.score_percentage) * scale) %> <%= inner_radius * Math.sin((1 - gauge.score_percentage) * scale) + key_benchmark_indicator_height %>
|
||||
A <%= inner_radius %> <%= inner_radius %> 0 0 0 <%= -inner_radius %> <%= key_benchmark_indicator_height %>
|
||||
L <%= -outer_radius %> <%= key_benchmark_indicator_height %>"
|
||||
fill="none"
|
||||
stroke="none"
|
||||
/>
|
||||
|
||||
<text
|
||||
class="gauge-title font-bitter fill-black"
|
||||
x="0"
|
||||
y="<% -10 + key_benchmark_indicator_height %>"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
>
|
||||
<%= gauge.title %>
|
||||
</text>
|
||||
|
||||
<path
|
||||
class="gauge-outline stroke-gray-2"
|
||||
d="M <%= -outer_radius %> <%= key_benchmark_indicator_height %>
|
||||
A <%= outer_radius %> <%= outer_radius %> 0 0 1 <%= outer_radius * Math.cos(0) %> <%= outer_radius * Math.sin(0) + key_benchmark_indicator_height %>
|
||||
L <%= inner_radius * Math.cos(0) %> <%= inner_radius * Math.sin(0) + key_benchmark_indicator_height %>
|
||||
A <%= inner_radius %> <%= inner_radius %> 0 0 0 <%= -inner_radius %> <%= key_benchmark_indicator_height %>
|
||||
L <%= -outer_radius %> <%= key_benchmark_indicator_height %>"
|
||||
fill="none"
|
||||
stroke-width="<%= stroke_width %>"
|
||||
/>
|
||||
|
||||
<line
|
||||
class="zone-benchmark stroke-gray-2"
|
||||
x1="<%= outer_radius * Math.cos((1 - gauge.key_benchmark_percentage) * scale) %>" y1="<%= outer_radius * Math.sin((1 - gauge.key_benchmark_percentage) * scale) + key_benchmark_indicator_height %>"
|
||||
x2="<%= inner_radius * Math.cos((1 - gauge.key_benchmark_percentage) * scale) %>" y2="<%= inner_radius * Math.sin((1 - gauge.key_benchmark_percentage) * scale) + key_benchmark_indicator_height %>"
|
||||
stroke-width="<%= stroke_width %>"
|
||||
/>
|
||||
|
||||
<path
|
||||
class="key-benchmark-indicator fill-black"
|
||||
d="M 0 <%= -(outer_radius - key_benchmark_indicator_height + 2) %>
|
||||
L <%= key_benchmark_indicator_height/Math.sqrt(3) %> <%= -outer_radius %>
|
||||
L <%= -key_benchmark_indicator_height/Math.sqrt(3) %> <%= -outer_radius %>
|
||||
L 0 <%= -(outer_radius - key_benchmark_indicator_height + 2) %>"
|
||||
transform="rotate(<%= 180.0 * gauge.key_benchmark_percentage - 90 %> 0 <%= key_benchmark_indicator_height %>)"
|
||||
stroke="none"
|
||||
/>
|
||||
</svg>
|
||||
7
app/views/browse/show.html.erb
Normal file
7
app/views/browse/show.html.erb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<h1><%= @category.name %></h1>
|
||||
|
||||
<% @category.subcategories.each do |subcategory| %>
|
||||
<h2><%= subcategory.name %></h2>
|
||||
<%= render partial: "gauge_graph", locals: { gauge: subcategory.gauge_presenter } %>
|
||||
<% end %>
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
<a href="/districts/<%= @district.slug %>/schools/<%= @school.slug %>/browse/teachers-and-leadership?year=2020-21">Browse</a>
|
||||
|
||||
<h1><%= @school.name %></h1>
|
||||
|
||||
<div class="fdr fjb fac">
|
||||
|
|
@ -28,7 +30,7 @@
|
|||
<div class="bg-beige mt-4 p-5">
|
||||
|
||||
<div>
|
||||
<h2 class="h1 red">Distance from benchmark</h2>
|
||||
<h2 class="h1 color-red">Distance from benchmark</h2>
|
||||
<p class="body-large">This graph shows how much a score is above or below the benchmark of any given scale.</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Rails.application.routes.draw do
|
|||
resources :districts do
|
||||
resources :schools, only: [:index, :show] do
|
||||
resources :dashboard, only: [:index]
|
||||
resources :browse, only: [:show]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
50
spec/factories.rb
Normal file
50
spec/factories.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
FactoryBot.define do
|
||||
factory :sqm_category do
|
||||
name { "A category" }
|
||||
end
|
||||
|
||||
factory :subcategory do
|
||||
name { "A subcategory" }
|
||||
sqm_category
|
||||
|
||||
factory :subcategory_with_measures do
|
||||
transient do
|
||||
measures_count { 2 }
|
||||
end
|
||||
after(:create) do |subcategory, evaluator|
|
||||
create_list(:measure, evaluator.measures_count, subcategory: subcategory)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
factory :measure do
|
||||
measure_id { rand.to_s }
|
||||
name { 'A Measure' }
|
||||
watch_low_benchmark { 2.0 }
|
||||
growth_low_benchmark { 3.0 }
|
||||
approval_low_benchmark { 4.0 }
|
||||
ideal_low_benchmark { 4.5 }
|
||||
subcategory
|
||||
end
|
||||
|
||||
factory :survey_item do
|
||||
survey_item_id { rand.to_s }
|
||||
measure
|
||||
end
|
||||
|
||||
factory :academic_year do
|
||||
range { '2050-51' }
|
||||
end
|
||||
|
||||
factory :school do
|
||||
name { "#{rand} School" }
|
||||
end
|
||||
|
||||
factory :survey_item_response do
|
||||
likert_score { 3 }
|
||||
response_id { rand.to_s }
|
||||
academic_year
|
||||
school
|
||||
survey_item
|
||||
end
|
||||
end
|
||||
|
|
@ -5,16 +5,21 @@ feature 'School dashboard', type: feature do
|
|||
let(:school) { School.find_by_slug 'winchester-high-school' }
|
||||
let(:school_in_same_district) { School.find_by_slug 'muraco-elementary-school' }
|
||||
|
||||
let(:category) { SqmCategory.find_by_name('Teachers & Leadership') }
|
||||
let(:subcategory) { Subcategory.find_by_name('Teachers & The Teaching Environment') }
|
||||
let(:measures_for_subcategory) { Measure.where(subcategory: subcategory) }
|
||||
let(:survey_items_for_subcategory) { SurveyItem.where(measure: measures_for_subcategory) }
|
||||
|
||||
let(:measure_1A_i) { Measure.find_by_measure_id('1A-i') }
|
||||
let(:measure_2A_i) { Measure.find_by_measure_id('2A-i') }
|
||||
let(:measure_4C_i) { Measure.find_by_measure_id('4C-i') }
|
||||
|
||||
let(:survey_item_1_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s }
|
||||
let(:survey_item_2_for_measure_1A_i) { SurveyItem.create measure: measure_1A_i, survey_item_id: rand.to_s }
|
||||
let(:survey_item_1_for_measure_2A_i) { SurveyItem.create measure: measure_2A_i, survey_item_id: rand.to_s }
|
||||
let(:survey_item_2_for_measure_2A_i) { SurveyItem.create measure: measure_2A_i, survey_item_id: rand.to_s }
|
||||
let(:survey_item_1_for_measure_4C_i) { SurveyItem.create measure: measure_4C_i, survey_item_id: rand.to_s }
|
||||
let(:survey_item_2_for_measure_4C_i) { SurveyItem.create measure: measure_4C_i, survey_item_id: rand.to_s }
|
||||
|
||||
let(:survey_items_for_measure_1A_i) { SurveyItem.where(measure: measure_1A_i) }
|
||||
let(:survey_items_for_measure_2A_i) { SurveyItem.where(measure: measure_2A_i) }
|
||||
let(:survey_items_for_measure_4C_i) { SurveyItem.where(measure: measure_4C_i) }
|
||||
|
||||
let(:measure_row_bars) { page.all('rect.measure-row-bar') }
|
||||
|
||||
|
|
@ -24,24 +29,27 @@ feature 'School dashboard', type: feature do
|
|||
let(:password) { 'winchester!' }
|
||||
|
||||
before :each do
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school,
|
||||
survey_item: survey_item_1_for_measure_1A_i, likert_score: 4
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school,
|
||||
survey_item: survey_item_2_for_measure_1A_i, likert_score: 5
|
||||
survey_items_for_measure_1A_i.each do |survey_item|
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4
|
||||
end
|
||||
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school,
|
||||
survey_item: survey_item_1_for_measure_2A_i, likert_score: 5
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school,
|
||||
survey_item: survey_item_2_for_measure_2A_i, likert_score: 5
|
||||
survey_items_for_measure_2A_i.each do |survey_item|
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 5
|
||||
end
|
||||
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school,
|
||||
survey_item: survey_item_1_for_measure_4C_i, likert_score: 1
|
||||
survey_items_for_measure_4C_i.each do |survey_item|
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 1
|
||||
end
|
||||
|
||||
survey_items_for_subcategory.each do |survey_item|
|
||||
SurveyItemResponse.create response_id: rand.to_s, academic_year: ay_2020_21, school: school, survey_item: survey_item, likert_score: 4
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'User authentication fails' do
|
||||
page.driver.browser.basic_authorize('wrong username', 'wrong password')
|
||||
|
||||
visit "/districts/winchester/schools/#{school.slug}/dashboard?year=2020-21"
|
||||
visit "/districts/#{district.slug}/schools/#{school.slug}/dashboard?year=2020-21"
|
||||
|
||||
expect(page).not_to have_text(school.name)
|
||||
end
|
||||
|
|
@ -59,7 +67,7 @@ feature 'School dashboard', type: feature do
|
|||
|
||||
expect(page).to have_text('Professional Qualifications')
|
||||
professional_qualifications_row = measure_row_bars.find { |item| item['data-for-measure-id'] == '1A-i' }
|
||||
expect(professional_qualifications_row['width']).to eq '20.66%'
|
||||
expect(professional_qualifications_row['width']).to eq '10.33%'
|
||||
expect(professional_qualifications_row['x']).to eq '50%'
|
||||
|
||||
expect(page).to have_text('Student Physical Safety')
|
||||
|
|
@ -78,6 +86,11 @@ feature 'School dashboard', type: feature do
|
|||
problem_solving_emphasis_row_index = measure_row_bars.find_index { |item| item['data-for-measure-id'] == '4C-i' }
|
||||
expect(student_physical_safety_row_index).to be < professional_qualifications_row_index
|
||||
expect(professional_qualifications_row_index).to be < problem_solving_emphasis_row_index
|
||||
|
||||
click_on 'Browse'
|
||||
|
||||
expect(page).to have_text('Teachers & Leadership')
|
||||
expect(page).to have_text('Approval')
|
||||
end
|
||||
|
||||
# visit photos_path
|
||||
|
|
@ -97,7 +110,7 @@ feature 'School dashboard', type: feature do
|
|||
expect(page.all('.school-options[selected]')[0].text).to eq 'Winchester High School'
|
||||
|
||||
school_options = page.all('.school-options')
|
||||
school_options.each_with_index do |school , index|
|
||||
school_options.each_with_index do |school , index|
|
||||
break if index == school_options.length-1
|
||||
expect(school.text).to be < school_options[index+1].text
|
||||
end
|
||||
|
|
@ -113,7 +126,7 @@ feature 'School dashboard', type: feature do
|
|||
expect(page.all('.district-options[selected]')[0].text).to eq 'Winchester'
|
||||
|
||||
district_options = page.all('.district-options')
|
||||
district_options.each_with_index do |district , index|
|
||||
district_options.each_with_index do |district , index|
|
||||
break if index == district_options.length-1
|
||||
expect(district.text).to be < district_options[index+1].text
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Measure, type: :model do
|
||||
it('has all the measures') do
|
||||
expect(Measure.count).to eq 30
|
||||
end
|
||||
|
||||
it 'returns the measure for the given measure id' do
|
||||
measure = Measure.find_by_measure_id('1A-i')
|
||||
|
||||
expect(measure.name).to eq 'Professional Qualifications'
|
||||
expect(measure.warning_zone).to eq Measure::Zone.new(1.0, 2.49, :warning)
|
||||
expect(measure.watch_zone).to eq Measure::Zone.new(2.49, 3.0, :watch)
|
||||
expect(measure.growth_zone).to eq Measure::Zone.new(3.0, 3.5, :growth)
|
||||
expect(measure.approval_zone).to eq Measure::Zone.new(3.5, 4.71, :approval)
|
||||
expect(measure.ideal_zone).to eq Measure::Zone.new(4.71, 5.0, :ideal)
|
||||
end
|
||||
|
||||
describe '#zone_for_score' do
|
||||
let(:measure) {
|
||||
Measure.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5
|
||||
}
|
||||
|
||||
context 'when the score is 1.0' do
|
||||
it 'returns the warning zone' do
|
||||
expect(measure.zone_for_score(1.0)).to eq measure.warning_zone
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the score is 5.0' do
|
||||
it 'returns the ideal zone' do
|
||||
expect(measure.zone_for_score(5.0)).to eq measure.ideal_zone
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the score is right on the benchmark' do
|
||||
it 'returns the higher zone' do
|
||||
expect(measure.zone_for_score(1.5)).to eq measure.watch_zone
|
||||
expect(measure.zone_for_score(2.5)).to eq measure.growth_zone
|
||||
expect(measure.zone_for_score(3.5)).to eq measure.approval_zone
|
||||
expect(measure.zone_for_score(4.5)).to eq measure.ideal_zone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
spec/presenters/category_presenter_spec.rb
Normal file
19
spec/presenters/category_presenter_spec.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe CategoryPresenter do
|
||||
let(:category_presenter) do
|
||||
subcategory1 = Subcategory.new(name: 'A subcategory')
|
||||
subcategory2 = Subcategory.new(name: 'Another subcategory')
|
||||
|
||||
category = SqmCategory.new(name: 'Some Category', subcategories: [subcategory1, subcategory2])
|
||||
return CategoryPresenter.new(category: category, academic_year: AcademicYear.new, school: School.new)
|
||||
end
|
||||
|
||||
it 'returns the name of the category' do
|
||||
expect(category_presenter.name).to eq 'Some Category'
|
||||
end
|
||||
|
||||
it 'maps subcategories to subcategory presenters' do
|
||||
expect(category_presenter.subcategories.map(&:name)).to eq ['A subcategory', 'Another subcategory']
|
||||
end
|
||||
end
|
||||
120
spec/presenters/gauge_presenter_spec.rb
Normal file
120
spec/presenters/gauge_presenter_spec.rb
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe GaugePresenter do
|
||||
# let(:academic_year) { create(:academic_year, range: '1989-90') }
|
||||
# let(:school) { create(:school, name: 'Best School') }
|
||||
# let(:subcategory_presenter) do
|
||||
# subcategory = create(:subcategory, name: 'A great subcategory')
|
||||
|
||||
# measure1 = create(:measure, watch_low_benchmark: 4, growth_low_benchmark: 4.25, approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75, subcategory: subcategory)
|
||||
# survey_item1 = create(:survey_item, measure: measure1)
|
||||
# create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 1)
|
||||
# create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 5)
|
||||
|
||||
# measure2 = create(:measure, watch_low_benchmark: 1.25, growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0, subcategory: subcategory)
|
||||
# survey_item2 = create(:survey_item, measure: measure2)
|
||||
# create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 1)
|
||||
# create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 5)
|
||||
|
||||
# create_survey_item_responses_for_different_years_and_schools(survey_item1)
|
||||
|
||||
# return SubcategoryPresenter.new(subcategory: subcategory, academic_year: academic_year, school: school)
|
||||
# end
|
||||
let(:scale) do
|
||||
Scale.new(
|
||||
watch_low_benchmark: 1.5,
|
||||
growth_low_benchmark: 2.5,
|
||||
approval_low_benchmark: 3.5,
|
||||
ideal_low_benchmark: 4.5,
|
||||
)
|
||||
end
|
||||
let(:score) { 3 }
|
||||
|
||||
|
||||
let(:gauge_presenter) { GaugePresenter.new(scale: scale, score: score) }
|
||||
|
||||
it 'returns the key benchmark percentage for the gauge' do
|
||||
expect(gauge_presenter.key_benchmark_percentage).to eq 0.625
|
||||
end
|
||||
|
||||
context 'when the given score is in the Warning zone for the given scale' do
|
||||
let(:score) { 1 }
|
||||
|
||||
it 'returns the title of the zone' do
|
||||
expect(gauge_presenter.title).to eq 'Warning'
|
||||
end
|
||||
|
||||
it 'returns the color class for the gauge' do
|
||||
expect(gauge_presenter.color_class).to eq 'fill-warning'
|
||||
end
|
||||
|
||||
it 'returns the score percentage for the gauge' do
|
||||
expect(gauge_presenter.score_percentage).to eq 0.0
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given score is in the Watch zone for the given scale' do
|
||||
let(:score) { 2 }
|
||||
|
||||
it 'returns the title of the zone' do
|
||||
expect(gauge_presenter.title).to eq 'Watch'
|
||||
end
|
||||
|
||||
it 'returns the color class for the gauge' do
|
||||
expect(gauge_presenter.color_class).to eq 'fill-watch'
|
||||
end
|
||||
|
||||
it 'returns the score percentage for the gauge' do
|
||||
expect(gauge_presenter.score_percentage).to eq 0.25
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given score is in the Growth zone for the given scale' do
|
||||
let(:score) { 3 }
|
||||
|
||||
it 'returns the title of the zone' do
|
||||
expect(gauge_presenter.title).to eq 'Growth'
|
||||
end
|
||||
|
||||
it 'returns the color class for the gauge' do
|
||||
expect(gauge_presenter.color_class).to eq 'fill-growth'
|
||||
end
|
||||
|
||||
it 'returns the score percentage for the gauge' do
|
||||
expect(gauge_presenter.score_percentage).to eq 0.5
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given score is in the Approval zone for the given scale' do
|
||||
let(:score) { 4 }
|
||||
|
||||
it 'returns the title of the zone' do
|
||||
expect(gauge_presenter.title).to eq 'Approval'
|
||||
end
|
||||
|
||||
it 'returns the color class for the gauge' do
|
||||
expect(gauge_presenter.color_class).to eq 'fill-approval'
|
||||
end
|
||||
|
||||
it 'returns the score percentage for the gauge' do
|
||||
expect(gauge_presenter.score_percentage).to eq 0.75
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given score is in the Ideal zone for the given scale' do
|
||||
let(:score) { 5 }
|
||||
|
||||
it 'returns the title of the zone' do
|
||||
expect(gauge_presenter.title).to eq 'Ideal'
|
||||
end
|
||||
|
||||
it 'returns the color class for the gauge' do
|
||||
expect(gauge_presenter.color_class).to eq 'fill-ideal'
|
||||
end
|
||||
|
||||
it 'returns the score percentage for the gauge' do
|
||||
expect(gauge_presenter.score_percentage).to eq 1.0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
30
spec/presenters/scale_spec.rb
Normal file
30
spec/presenters/scale_spec.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Scale do
|
||||
describe '#zone_for_score' do
|
||||
let(:scale) {
|
||||
Scale.new watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5
|
||||
}
|
||||
|
||||
context 'when the score is 1.0' do
|
||||
it 'returns the warning zone' do
|
||||
expect(scale.zone_for_score(1.0)).to eq scale.warning_zone
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the score is 5.0' do
|
||||
it 'returns the ideal zone' do
|
||||
expect(scale.zone_for_score(5.0)).to eq scale.ideal_zone
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the score is right on the benchmark' do
|
||||
it 'returns the higher zone' do
|
||||
expect(scale.zone_for_score(1.5)).to eq scale.watch_zone
|
||||
expect(scale.zone_for_score(2.5)).to eq scale.growth_zone
|
||||
expect(scale.zone_for_score(3.5)).to eq scale.approval_zone
|
||||
expect(scale.zone_for_score(4.5)).to eq scale.ideal_zone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
37
spec/presenters/subcategory_presenter_spec.rb
Normal file
37
spec/presenters/subcategory_presenter_spec.rb
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe SubcategoryPresenter do
|
||||
let(:academic_year) { create(:academic_year, range: '1989-90') }
|
||||
let(:school) { create(:school, name: 'Best School') }
|
||||
let(:subcategory_presenter) do
|
||||
subcategory = create(:subcategory, name: 'A great subcategory')
|
||||
|
||||
measure1 = create(:measure, watch_low_benchmark: 4, growth_low_benchmark: 4.25, approval_low_benchmark: 4.5, ideal_low_benchmark: 4.75, subcategory: subcategory)
|
||||
survey_item1 = create(:survey_item, measure: measure1)
|
||||
create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 1)
|
||||
create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 5)
|
||||
|
||||
measure2 = create(:measure, watch_low_benchmark: 1.25, growth_low_benchmark: 1.5, approval_low_benchmark: 1.75, ideal_low_benchmark: 2.0, subcategory: subcategory)
|
||||
survey_item2 = create(:survey_item, measure: measure2)
|
||||
create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 1)
|
||||
create(:survey_item_response, survey_item: survey_item2, academic_year: academic_year, school: school, likert_score: 5)
|
||||
|
||||
create_survey_item_responses_for_different_years_and_schools(survey_item1)
|
||||
|
||||
return SubcategoryPresenter.new(subcategory: subcategory, academic_year: academic_year, school: school)
|
||||
end
|
||||
|
||||
it 'returns the name of the subcategory' do
|
||||
expect(subcategory_presenter.name).to eq 'A great subcategory'
|
||||
end
|
||||
|
||||
it 'returns a gauge presenter responsible for the aggregate survey item response likert scores' do
|
||||
expect(subcategory_presenter.gauge_presenter.title).to eq 'Growth'
|
||||
end
|
||||
|
||||
def create_survey_item_responses_for_different_years_and_schools(survey_item)
|
||||
create(:survey_item_response, survey_item: survey_item, school: school, likert_score: 1)
|
||||
create(:survey_item_response, survey_item: survey_item, academic_year: academic_year, likert_score: 1)
|
||||
end
|
||||
|
||||
end
|
||||
3
spec/support/factory_bot.rb
Normal file
3
spec/support/factory_bot.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
RSpec.configure do |config|
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
end
|
||||
39
spec/views/browse/show.html.erb_spec.rb
Normal file
39
spec/views/browse/show.html.erb_spec.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'browse/show.html.erb' do
|
||||
before :each do
|
||||
academic_year = create(:academic_year, range: '1989-90')
|
||||
school = create(:school, name: 'Best School')
|
||||
|
||||
category = create(:sqm_category, name: 'Some Category')
|
||||
|
||||
subcategory1 = create(:subcategory, sqm_category: category, name: 'A subcategory')
|
||||
subcategory2 = create(:subcategory_with_measures, sqm_category: category, name: 'Another subcategory')
|
||||
|
||||
measure1 = create(:measure, subcategory: subcategory1, watch_low_benchmark: 1.5, growth_low_benchmark: 2.5, approval_low_benchmark: 3.5, ideal_low_benchmark: 4.5)
|
||||
|
||||
survey_item1 = create(:survey_item, measure: measure1)
|
||||
|
||||
survey_item_response1 = create(:survey_item_response, survey_item: survey_item1, academic_year: academic_year, school: school, likert_score: 3)
|
||||
|
||||
assign :category, CategoryPresenter.new(category: category, academic_year: academic_year, school: school)
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
it 'renders the category name' do
|
||||
expect(rendered).to match /Some Category/
|
||||
end
|
||||
|
||||
context 'for each subcategory' do
|
||||
it 'renders the subcategory name' do
|
||||
expect(rendered).to match /A subcategory/
|
||||
expect(rendered).to match /Another subcategory/
|
||||
end
|
||||
|
||||
it 'renders the zone title and fill color for the gauge graph' do
|
||||
expect(rendered).to match /Growth/
|
||||
expect(rendered).to match /fill-growth/
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue