Show partial data indicators on variance chart

pull/1/head
Nelson Jovel 4 years ago committed by Liam Morley
parent 8205578267
commit aeb6a45a45

@ -84,3 +84,11 @@
stroke: $gray-2;
fill: $gray-3;
}
.limited-availability-indicator {
color: white;
background-color: black;
line-height: 30px;
border-radius: 50%;
width: 20px;
}

@ -1,111 +0,0 @@
// animating icons
// --------------------------
.#{$fa-css-prefix}-beat {
animation-name: #{$fa-css-prefix}-beat;
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0);
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out);
}
.#{$fa-css-prefix}-fade {
animation-name: #{$fa-css-prefix}-fade;
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0);
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1));
}
.#{$fa-css-prefix}-flash {
animation-name: #{$fa-css-prefix}-flash;
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0);
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1));
}
.#{$fa-css-prefix}-flip {
animation-name: #{$fa-css-prefix}-flip;
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0);
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out);
}
.#{$fa-css-prefix}-spin {
animation-name: #{$fa-css-prefix}-spin;
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0);
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 2s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear);
}
.#{$fa-css-prefix}-spin-reverse {
--#{$fa-css-prefix}-animation-direction: reverse;
}
.#{$fa-css-prefix}-pulse,
.#{$fa-css-prefix}-spin-pulse {
animation-name: #{$fa-css-prefix}-spin;
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, steps(8));
}
// if agent or operating system prefers reduced motion, disable animations
// see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/
// see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
.#{$fa-css-prefix}-beat,
.#{$fa-css-prefix}-fade,
.#{$fa-css-prefix}-flash,
.#{$fa-css-prefix}-flip,
.#{$fa-css-prefix}-pulse,
.#{$fa-css-prefix}-spin,
.#{$fa-css-prefix}-spin-pulse {
animation-delay: -1ms;
animation-duration: 1ms;
animation-iteration-count: 1;
transition-delay: 0s;
transition-duration: 0s;
}
}
@keyframes #{$fa-css-prefix}-beat {
0%, 90% { transform: scale(1); }
45% { transform: scale(var(--#{$fa-css-prefix}-beat-scale, 1.25)); }
}
@keyframes #{$fa-css-prefix}-fade {
50% { opacity: var(--#{$fa-css-prefix}-fade-opacity, 0.4); }
}
@keyframes #{$fa-css-prefix}-flash {
0%, 100% {
opacity: var(--#{$fa-css-prefix}-flash-opacity, 0.4);
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(var(--#{$fa-css-prefix}-flash-scale, 1.125));
}
}
@keyframes #{$fa-css-prefix}-flip {
50% {
transform: rotate3d(var(--#{$fa-css-prefix}-flip-x, 0), var(--#{$fa-css-prefix}-flip-y, 1), var(--#{$fa-css-prefix}-flip-z, 0), var(--#{$fa-css-prefix}-flip-angle, -180deg));
}
}
@keyframes #{$fa-css-prefix}-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

@ -1,20 +0,0 @@
// bordered + pulled icons
// -------------------------
.#{$fa-css-prefix}-border {
border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color});
border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius});
border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style});
border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width});
padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding});
}
.#{$fa-css-prefix}-pull-left {
float: left;
margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin});
}
.#{$fa-css-prefix}-pull-right {
float: right;
margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin});
}

@ -1,33 +0,0 @@
// base icon class definition
// -------------------------
.#{$fa-css-prefix} {
font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}');
font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style});
}
.#{$fa-css-prefix},
.fas,
.fa-solid,
.far,
.fa-regular,
.fal,
.fa-light,
.fat,
.fa-thin,
.fad,
.fa-duotone,
.fab,
.fa-brands {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: var(--#{$fa-css-prefix}-display, #{$fa-display});
font-style: normal;
font-variant: normal;
line-height: 1;
text-rendering: auto;
}
%fa-icon {
@include fa-icon;
}

@ -1,7 +0,0 @@
// fixed-width icons
// -------------------------
.#{$fa-css-prefix}-fw {
text-align: center;
width: $fa-fw-width;
}

@ -1,51 +0,0 @@
// functions
// --------------------------
// Originally obtained from the Bootstrap https://github.com/twbs/bootstrap
//
// Licensed under: The MIT License (MIT)
//
// Copyright (c) 2011-2021 Twitter, Inc.
// Copyright (c) 2011-2021 The Bootstrap Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
@function divide($dividend, $divisor, $precision: 10) {
$sign: if($dividend > 0 and $divisor > 0, 1, -1);
$dividend: abs($dividend);
$divisor: abs($divisor);
$quotient: 0;
$remainder: $dividend;
@if $dividend == 0 {
@return 0;
}
@if $divisor == 0 {
@error "Cannot divide by 0";
}
@if $divisor == 1 {
@return $dividend;
}
@while $remainder >= $divisor {
$quotient: $quotient + 1;
$remainder: $remainder - $divisor;
}
@if $remainder > 0 and $precision > 0 {
$remainder: divide($remainder * 10, $divisor, $precision - 1) * .1;
}
@return ($quotient + $remainder) * $sign;
}

@ -1,9 +0,0 @@
// specific icon class definition
// -------------------------
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
readers do not read off random characters that represent icons */
@each $name, $icon in $fa-icons {
.#{$fa-css-prefix}-#{$name}::before { content: fa-content($icon); }
}

@ -1,18 +0,0 @@
// icons in a list
// -------------------------
.#{$fa-css-prefix}-ul {
list-style-type: none;
margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin});
padding-left: 0;
> li { position: relative; }
}
.#{$fa-css-prefix}-li {
left: calc(var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}) * -1);
position: absolute;
text-align: center;
width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width});
line-height: inherit;
}

@ -1,42 +0,0 @@
// mixins
// --------------------------
// base rendering for an icon
@mixin fa-icon {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: inline-block;
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
}
// sets relative font-sizing and alignment (in _sizing)
@mixin fa-size ($font-size) {
font-size: (divide($font-size, $fa-size-scale-base)) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base
line-height: (divide(1, $font-size)) * 1em; // sets the line-height of the icon back to that of it's parent
vertical-align: ((divide(6, $font-size)) - (divide(3, 8))) * 1em; // vertically centers the icon taking into account the surrounding text's descender
}
// only display content to screen readers
// see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/
// see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/
@mixin fa-sr-only() {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
// use in conjunction with .sr-only to only display content when it's focused
@mixin fa-sr-only-focusable() {
&:not(:focus) {
@include fa-sr-only();
}
}

@ -1,31 +0,0 @@
// rotating + flipping icons
// -------------------------
.#{$fa-css-prefix}-rotate-90 {
transform: rotate(90deg);
}
.#{$fa-css-prefix}-rotate-180 {
transform: rotate(180deg);
}
.#{$fa-css-prefix}-rotate-270 {
transform: rotate(270deg);
}
.#{$fa-css-prefix}-flip-horizontal {
transform: scale(-1, 1);
}
.#{$fa-css-prefix}-flip-vertical {
transform: scale(1, -1);
}
.#{$fa-css-prefix}-flip-both,
.#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical {
transform: scale(-1, -1);
}
.#{$fa-css-prefix}-rotate-by {
transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, none));
}

@ -1,14 +0,0 @@
// screen-reader utilities
// -------------------------
// only display content to screen readers
.sr-only,
.fa-sr-only {
@include fa-sr-only;
}
// use in conjunction with .sr-only to only display content when it's focused
.sr-only-focusable,
.fa-sr-only-focusable {
@include fa-sr-only-focusable;
}

File diff suppressed because it is too large Load Diff

@ -1,16 +0,0 @@
// sizing icons
// -------------------------
// literal magnification scale
@for $i from 1 through 10 {
.#{$fa-css-prefix}-#{$i}x {
font-size: $i * 1em;
}
}
// step-based scale (with alignment)
@each $size, $value in $fa-sizes {
.#{$fa-css-prefix}-#{$size} {
@include fa-size($value);
}
}

@ -1,32 +0,0 @@
// stacking icons
// -------------------------
.#{$fa-css-prefix}-stack {
display: inline-block;
height: 2em;
line-height: 2em;
position: relative;
vertical-align: $fa-stack-vertical-align;
width: $fa-stack-width;
}
.#{$fa-css-prefix}-stack-1x,
.#{$fa-css-prefix}-stack-2x {
left: 0;
position: absolute;
text-align: center;
width: 100%;
z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index});
}
.#{$fa-css-prefix}-stack-1x {
line-height: inherit;
}
.#{$fa-css-prefix}-stack-2x {
font-size: 2em;
}
.#{$fa-css-prefix}-inverse {
color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse});
}

File diff suppressed because it is too large Load Diff

@ -1,25 +0,0 @@
/*!
* Font Awesome Free 6.0.0-beta2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@import 'functions';
@import 'variables';
@font-face {
font-family: 'Font Awesome 6 Brands';
font-style: normal;
font-weight: 400;
font-display: $fa-font-display;
src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'),
url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype');
}
.fab,
.fa-brands {
font-family: 'Font Awesome 6 Brands';
font-weight: 400;
}
@each $name, $icon in $fa-brand-icons {
.#{$fa-css-prefix}-#{$name}:before { content: fa-content($icon); }
}

@ -1,20 +0,0 @@
/*!
* Font Awesome Free 6.0.0-beta2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
// Font Awesome core compile (Web Fonts-based)
// -------------------------
@import 'functions';
@import 'variables';
@import 'mixins';
@import 'core';
@import 'sizing';
@import 'fixed-width';
@import 'list';
@import 'bordered-pulled';
@import 'animated';
@import 'rotated-flipped';
@import 'stacked';
@import 'icons';
@import 'screen-reader';

@ -1,21 +0,0 @@
/*!
* Font Awesome Free 6.0.0-beta2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@import 'functions';
@import 'variables';
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 400;
font-display: $fa-font-display;
src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'),
url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype');
}
.far,
.fa-regular {
font-family: 'Font Awesome 6 Free';
font-weight: 400;
}

@ -1,21 +0,0 @@
/*!
* Font Awesome Free 6.0.0-beta2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
@import 'functions';
@import 'variables';
@font-face {
font-family: 'Font Awesome 6 Free';
font-style: normal;
font-weight: 900;
font-display: $fa-font-display;
src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'),
url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype');
}
.fas,
.fa-solid {
font-family: 'Font Awesome 6 Free';
font-weight: 900;
}

@ -1,10 +0,0 @@
/*!
* Font Awesome Free 6.0.0-beta2 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
// V4 shims compile (Web Fonts-based)
// -------------------------
@import 'functions';
@import 'variables';
@import 'shims';

@ -6,8 +6,8 @@
@import "dashboard";
@import "navigation";
@import "buttons";
@import "scss/fontawesome.scss";
@import "scss/solid.scss";
@import "@fortawesome/fontawesome-free/scss/fontawesome";
@import "@fortawesome/fontawesome-free/scss/solid";
@import "footer";
@import "forms";

@ -8,7 +8,7 @@ class DashboardController < SqmApplicationController
private
def presenter_for_measure(measure)
score = SurveyItemResponse.score_for_measure(measure: measure, school: @school, academic_year: @academic_year).average
score = SurveyItemResponse.score_for_measure(measure: measure, school: @school, academic_year: @academic_year)
VarianceChartRowPresenter.new(measure: measure, score: score)
end

@ -42,4 +42,13 @@ module VarianceHelper
def zone_width_percentage
100.0/zones.size
end
def availability_indicator_percentage
3
end
def partial_data_indicator_size
16
end
end

@ -5,10 +5,12 @@ class Measure < ActiveRecord::Base
has_many :survey_item_responses, through: :survey_items
scope :source_includes_survey_items, ->() { joins(:survey_items).uniq }
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) }
none? do |measure|
SurveyItemResponse.sufficient_data?(measure: measure, school: school, academic_year: academic_year)
end
end
def teacher_survey_items
@ -27,4 +29,15 @@ class Measure < ActiveRecord::Base
student_survey_items.any?
end
def includes_admin_data_items?
admin_data_items.any?
end
def sources
sources = []
sources << :admin_data if includes_admin_data_items?
sources << :student_surveys if includes_student_survey_items?
sources << :teacher_surveys if includes_teacher_survey_items?
sources
end
end

@ -0,0 +1,2 @@
class Score < Struct.new(:average, :meets_teacher_threshold?, :meets_student_threshold?)
end

@ -14,8 +14,6 @@ class SurveyItemResponse < ActiveRecord::Base
.average(:likert_score)
end
Score = Struct.new(:average, :meets_teacher_threshold?, :meets_student_threshold?)
def self.score_for_measure(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

@ -5,7 +5,9 @@ class VarianceChartRowPresenter
def initialize(measure:, score:)
@measure = measure
@score = score
@score = score.average
@meets_teacher_threshold = score.meets_teacher_threshold?
@meets_student_threshold = score.meets_student_threshold?
end
def sufficient_data?
@ -31,7 +33,7 @@ class VarianceChartRowPresenter
def x_offset
case zone.type
when :ideal, :approval
"60%"
'60%'
else
"#{((0.6 - bar_width_percentage) * 100).abs.round(2)}%"
end
@ -48,8 +50,20 @@ class VarianceChartRowPresenter
end
end
def <=>(other_presenter)
other_presenter.order <=> order
def <=>(other)
other.order <=> order
end
def show_partial_data_indicator?
partial_data_sources.present?
end
def partial_data_sources
Array.new.tap do |sources|
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 << 'administrative data' if @measure.includes_admin_data_items?
end
end
private
@ -86,7 +100,7 @@ class VarianceChartRowPresenter
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,
ideal_low_benchmark: @measure.ideal_low_benchmark
)
scale.zone_for_score(@score)
end

@ -31,7 +31,7 @@
filter="url(#inset-shadow)"
>
<g id="zone-headings">
<%= zones.each_with_index do |zone, index| %>
<% zones.each_with_index do |zone, index| %>
<text
class="zone-header"
x="<%= index * zone_width_percentage + zone_width_percentage / 2.0 %>%"
@ -45,7 +45,7 @@
</g>
<g id="zone-background" transform="translate(0, <%= heading_gutter %>)">
<%= zones.each_with_index do |zone, index| %>
<% zones.each_with_index do |zone, index| %>
<rect
id="<%= zone %>-zone"
class="zone-background bg-fill-<%= zone %>"
@ -61,14 +61,26 @@
</svg>
<g id="measure-rows">
<svg id=measure-row-limited-availability-indicator x="0" y="<%= heading_gutter %>">
<% displayed_presenters.each_with_index do |presenter, index| %>
<% if presenter.show_partial_data_indicator? %>
<use xlink:href="/fontawesome-free/sprites/solid.svg#circle-exclamation" width="<%= partial_data_indicator_size %>"
height="<%= partial_data_indicator_size %>"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-content="The following sources are not included in this measure due to insufficient data: <%= presenter.partial_data_sources.join(' and ') %>."
x="<%= partial_data_indicator_size / 2 %>"
y="<%= index * measure_row_height + measure_row_height / 2 - partial_data_indicator_size / 2 %>">
</use>
<% end %>
<% end %>
</svg>
<svg id="measure-row-labels" x="0" y=<%= heading_gutter %>>
<%= displayed_presenters.each_with_index do |presenter, index| %>
<% displayed_presenters.each_with_index do |presenter, index| %>
<text
class="font-cabin"
x="<%= label_width_percentage %>%"
dx="<%= -1 * label_padding_right %>"
x="<%= availability_indicator_percentage %>%"
y="<%= index * measure_row_height + measure_row_height / 2 %>"
text-anchor="end"
dominant-baseline="middle"
data-variance-row-label
>
@ -83,7 +95,7 @@
y="<%= heading_gutter %>"
width="<%= graph_width_percentage %>%"
>
<%= displayed_presenters.each_with_index do |presenter, index| %>
<% displayed_presenters.each_with_index do |presenter, index| %>
<rect
class="measure-row-bar <%= presenter.bar_color %>"
x="<%= presenter.x_offset %>"

@ -3,6 +3,7 @@
"private": "true",
"dependencies": {
"@babel/preset-env": "^7.15.8",
"@fortawesome/fontawesome-free": "^6.0.0-beta3",
"@popperjs/core": "^2.10.2",
"@rails/actioncable": "^6.0.0",
"@rails/activestorage": "^6.0.0",

@ -0,0 +1 @@
../node_modules/@fortawesome/fontawesome-free/

@ -8,7 +8,8 @@ describe VarianceChartRowPresenter do
let(:ideal_low_benchmark) { 3.8 }
let(:measure) {
Measure.new(
create(
:measure,
name: 'Some Title',
watch_low_benchmark: watch_low_benchmark,
growth_low_benchmark: growth_low_benchmark,
@ -27,8 +28,8 @@ describe VarianceChartRowPresenter do
end
end
context('when the score is in the Ideal zone') do
let(:score) { 4.4 }
context 'when the score is in the Ideal zone' do
let(:score) { Score.new(4.4, true, true) }
it_behaves_like 'measure_name'
@ -45,8 +46,8 @@ describe VarianceChartRowPresenter do
end
end
context('when the score is in the Approval zone') do
let(:score) { 3.7 }
context 'when the score is in the Approval zone' do
let(:score) { Score.new(3.7, true, true) }
it_behaves_like 'measure_name'
@ -63,8 +64,8 @@ describe VarianceChartRowPresenter do
end
end
context('when the score is in the Growth zone') do
let(:score) { 3.2 }
context 'when the score is in the Growth zone' do
let(:score) { Score.new(3.2, true, true) }
it_behaves_like 'measure_name'
@ -83,8 +84,8 @@ describe VarianceChartRowPresenter do
end
end
context('when the score is in the Watch zone') do
let(:score) { 2.9 }
context 'when the score is in the Watch zone' do
let(:score) { Score.new(2.9, true, true) }
it_behaves_like 'measure_name'
@ -103,8 +104,8 @@ describe VarianceChartRowPresenter do
end
end
context('when the score is in the Warning zone') do
let(:score) { 1.0 }
context 'when the score is in the Warning zone' do
let(:score) { Score.new(1.0, true, true) }
it_behaves_like 'measure_name'
@ -123,17 +124,88 @@ describe VarianceChartRowPresenter do
end
end
context 'when a measure does not contain admin data items' do
let(:score) { Score.new(nil, false, false) }
it 'it does not show a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be false
end
end
context 'when a measure contains admin data items' do
before :each do
create :admin_data_item, measure: measure
end
let(:score) { Score.new(nil, false, false) }
it 'shows a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be true
expect(presenter.partial_data_sources).to eq ['administrative data']
end
end
context 'when a measure contains teacher survey items' do
before :each do
create :teacher_survey_item, measure: measure
end
context 'when there are insufficient teacher survey item responses' do
let(:score) { Score.new(nil, false, true) }
it 'shows a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be true
expect(presenter.partial_data_sources).to eq ['teacher survey results']
end
end
context 'when there are sufficient teacher survey item responses' do
let(:score) { Score.new(nil, true, true) }
it 'does not show a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be false
end
end
end
context 'when a measure contains student survey items' do
before :each do
create :student_survey_item, measure: measure
end
context 'when there are insufficient student survey item responses' do
let(:score) { Score.new(nil, true, false) }
it 'shows a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be true
expect(presenter.partial_data_sources).to eq ['student survey results']
end
context 'where there are also admin data items' do
before :each do
create :admin_data_item, measure: measure
end
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']
end
end
end
context 'When there are sufficient student survey item responses' do
let(:score) { Score.new(nil, true, true) }
it 'does not show a partial data indicator' do
expect(presenter.show_partial_data_indicator?).to be false
end
end
end
context 'sorting scores' do
it 'selects a longer bar before a shorter bar for measures in the approval/ideal zones' do
approval_presenter = VarianceChartRowPresenter.new measure: measure, score: 3.7
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: 4.4
approval_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(3.7, true, true)
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(4.4, true, true)
expect(ideal_presenter <=> approval_presenter).to be < 0
expect([approval_presenter, ideal_presenter].sort).to eq [ideal_presenter, approval_presenter]
end
it 'selects a warning bar below a ideal bar' do
warning_presenter = VarianceChartRowPresenter.new measure: measure, score: 1.0
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: 5.0
warning_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(1.0, true, true)
ideal_presenter = VarianceChartRowPresenter.new measure: measure, score: Score.new(5.0, true, true)
expect(warning_presenter <=> ideal_presenter).to be > 0
expect([warning_presenter, ideal_presenter].sort).to eq [ideal_presenter, warning_presenter]
end

@ -17,10 +17,10 @@ describe 'dashboard/index.html.erb' do
context 'when some presenters have a nil score' do
let(:variance_chart_row_presenters) {
[
VarianceChartRowPresenter.new(measure: support_for_teaching, score: nil),
VarianceChartRowPresenter.new(measure: create(:measure, name: 'Should Be Displayed', measure_id: 'should-be-displayed'), score: rand),
VarianceChartRowPresenter.new(measure: effective_leadership, score: nil),
VarianceChartRowPresenter.new(measure: professional_qualifications, score: nil)
VarianceChartRowPresenter.new(measure: support_for_teaching, score: Score.new),
VarianceChartRowPresenter.new(measure: create(:measure, name: 'Should Be Displayed', measure_id: 'should-be-displayed'), score: Score.new(rand)),
VarianceChartRowPresenter.new(measure: effective_leadership, score: Score.new),
VarianceChartRowPresenter.new(measure: professional_qualifications, score: Score.new)
]
}
@ -42,7 +42,7 @@ describe 'dashboard/index.html.erb' do
context 'when all the presenters have a non-nil score' do
let(:variance_chart_row_presenters) {
[
VarianceChartRowPresenter.new(measure: create(:measure, name: 'Display Me', measure_id: 'display-me'), score: rand)
VarianceChartRowPresenter.new(measure: create(:measure, name: 'Display Me', measure_id: 'display-me'), score: Score.new(rand))
]
}

@ -8,8 +8,8 @@ describe 'dashboard/_variance_chart.html.erb' do
before :each do
presenters = [
VarianceChartRowPresenter.new(measure: lower_scoring_measure, score: 1),
VarianceChartRowPresenter.new(measure: higher_scoring_measure, score: 5)
VarianceChartRowPresenter.new(measure: lower_scoring_measure, score: Score.new(1)),
VarianceChartRowPresenter.new(measure: higher_scoring_measure, score: Score.new(5))
]
render partial: 'variance_chart', locals: { presenters: presenters }

@ -894,6 +894,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@fortawesome/fontawesome-free@^6.0.0-beta3":
version "6.0.0-beta3"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.0.0-beta3.tgz#120e4a158a0de983924ce151bc35f27de46398b7"
integrity sha512-4SqOuhC8tSLeQvbW1nDmq6T7+8vdSgHy/w7PRwCFzMQCbKuYFIir/3UuWsV1QblX1lt7SGlSgwbaCv9XhRt8HA==
"@istanbuljs/load-nyc-config@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"

Loading…
Cancel
Save