parent
435bc4a5be
commit
a71ebbc4e4
@ -1,107 +1,107 @@
|
|||||||
source "https://rubygems.org"
|
source 'https://rubygems.org'
|
||||||
ruby "3.2.1"
|
ruby '3.2.1'
|
||||||
|
|
||||||
git_source(:github) do |repo_name|
|
git_source(:github) do |repo_name|
|
||||||
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?('/')
|
||||||
"https://github.com/#{repo_name}.git"
|
"https://github.com/#{repo_name}.git"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||||
gem "rails", "~> 7.0.4"
|
gem 'rails', '~> 7.0.4'
|
||||||
gem "sprockets-rails"
|
gem 'sprockets-rails'
|
||||||
|
|
||||||
gem "pg"
|
gem 'pg'
|
||||||
|
|
||||||
# Use Puma as the app server
|
# Use Puma as the app server
|
||||||
gem "puma", ">= 5.6.4"
|
gem 'puma', '>= 5.6.4'
|
||||||
# Use Uglifier as compressor for JavaScript assets
|
# Use Uglifier as compressor for JavaScript assets
|
||||||
gem "uglifier", ">= 1.3.0"
|
gem 'uglifier', '>= 1.3.0'
|
||||||
# See https://github.com/rails/execjs#readme for more supported runtimes
|
# See https://github.com/rails/execjs#readme for more supported runtimes
|
||||||
# Use jquery as the JavaScript library
|
# Use jquery as the JavaScript library
|
||||||
gem "jquery-rails"
|
gem 'jquery-rails'
|
||||||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
||||||
gem "jbuilder", "~> 2.5"
|
gem 'jbuilder', '~> 2.5'
|
||||||
# Use Redis adapter to run Action Cable in production
|
# Use Redis adapter to run Action Cable in production
|
||||||
gem "redis", "~> 3.0"
|
gem 'redis', '~> 3.0'
|
||||||
# Use ActiveModel has_secure_password
|
# Use ActiveModel has_secure_password
|
||||||
# gem 'bcrypt', '~> 3.1.7'
|
# gem 'bcrypt', '~> 3.1.7'
|
||||||
|
|
||||||
gem "nokogiri", ">= 1.13.4"
|
gem 'nokogiri', '>= 1.13.4'
|
||||||
|
|
||||||
gem "bootsnap", require: false
|
gem 'bootsnap', require: false
|
||||||
|
|
||||||
gem "haml"
|
gem 'haml'
|
||||||
|
|
||||||
gem "friendly_id", "~> 5.1.0"
|
gem 'friendly_id', '~> 5.1.0'
|
||||||
|
|
||||||
gem "newrelic_rpm"
|
gem 'newrelic_rpm'
|
||||||
|
|
||||||
gem "devise"
|
gem 'devise'
|
||||||
|
|
||||||
gem "omniauth"
|
gem 'omniauth'
|
||||||
|
|
||||||
gem "twilio-ruby", "~> 4.11.1"
|
gem 'twilio-ruby', '~> 4.11.1'
|
||||||
|
|
||||||
gem "activerecord-import"
|
gem 'activerecord-import'
|
||||||
|
|
||||||
gem "jsbundling-rails"
|
gem 'jsbundling-rails'
|
||||||
|
|
||||||
gem "cssbundling-rails"
|
gem 'cssbundling-rails'
|
||||||
|
|
||||||
gem "turbo-rails"
|
gem 'turbo-rails'
|
||||||
|
|
||||||
gem "stimulus-rails"
|
gem 'stimulus-rails'
|
||||||
|
|
||||||
gem "watir"
|
gem 'watir'
|
||||||
|
|
||||||
gem "selenium-webdriver", "~> 4.4"
|
gem 'selenium-webdriver', '~> 4.4'
|
||||||
gem "net-sftp"
|
gem 'net-sftp'
|
||||||
gem "ed25519"
|
gem 'ed25519'
|
||||||
gem "bcrypt_pbkdf"
|
gem 'bcrypt_pbkdf'
|
||||||
|
|
||||||
gem "standard_deviation"
|
gem 'standard_deviation'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
||||||
gem "byebug", platform: :mri
|
gem 'byebug', platform: :mri
|
||||||
gem "factory_bot_rails"
|
gem 'factory_bot_rails'
|
||||||
gem "parallel_tests"
|
gem 'parallel_tests'
|
||||||
gem "rack-mini-profiler"
|
gem 'rack-mini-profiler'
|
||||||
gem "rspec-rails", "~> 5.1.0"
|
gem 'rspec-rails', '~> 5.1.0'
|
||||||
gem "debug", platforms: %i[mri mingw x64_mingw]
|
gem 'debug', platforms: %i[mri mingw x64_mingw]
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
|
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
|
||||||
gem "brakeman"
|
gem 'brakeman'
|
||||||
gem "bullet"
|
gem 'bullet'
|
||||||
gem "erb_lint", require: false
|
gem 'erb_lint', require: false
|
||||||
gem "erblint-github"
|
gem 'erblint-github'
|
||||||
gem "guard"
|
gem 'guard'
|
||||||
gem "guard-rspec", require: false
|
gem 'guard-rspec', require: false
|
||||||
gem "guard-livereload", "~> 2.5", require: false
|
gem 'guard-livereload', '~> 2.5', require: false
|
||||||
gem "rack-livereload"
|
gem 'rack-livereload'
|
||||||
gem "listen", "~> 3.0.5"
|
gem 'listen', '~> 3.0.5'
|
||||||
gem "nested_scaffold"
|
gem 'nested_scaffold'
|
||||||
# gem 'reek', require: false
|
# gem 'reek', require: false
|
||||||
gem "rubocop", require: false
|
gem 'rubocop', require: false
|
||||||
gem "seed_dump"
|
gem 'seed_dump'
|
||||||
gem "solargraph-reek"
|
gem 'solargraph-reek'
|
||||||
gem "spring"
|
gem 'spring'
|
||||||
gem "web-console"
|
gem 'web-console'
|
||||||
end
|
end
|
||||||
|
|
||||||
group "test" do
|
group 'test' do
|
||||||
gem "apparition", github: "twalpole/apparition", ref: "ca86be4d54af835d531dbcd2b86e7b2c77f85f34"
|
gem 'apparition', github: 'twalpole/apparition', ref: 'ca86be4d54af835d531dbcd2b86e7b2c77f85f34'
|
||||||
gem "capybara"
|
gem 'capybara'
|
||||||
gem "database_cleaner"
|
gem 'database_cleaner'
|
||||||
gem "launchy"
|
gem 'launchy'
|
||||||
gem "rails-controller-testing"
|
gem 'rails-controller-testing'
|
||||||
gem "simplecov", require: false
|
gem 'simplecov', require: false
|
||||||
gem "timecop"
|
gem 'timecop'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
|
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
|
||||||
|
|
||||||
gem "reline", "~> 0.3.2"
|
gem 'reline', '~> 0.3.2'
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
class ResponseRatePresenter
|
||||||
|
attr_reader :focus, :academic_year, :school, :survey_items
|
||||||
|
|
||||||
|
def initialize(focus:, academic_year:, school:)
|
||||||
|
@focus = focus
|
||||||
|
@academic_year = academic_year
|
||||||
|
@school = school
|
||||||
|
@survey_items = SurveyItem.student_survey_items if focus == :student
|
||||||
|
@survey_items = SurveyItem.teacher_survey_items if focus == :teacher
|
||||||
|
end
|
||||||
|
|
||||||
|
def date
|
||||||
|
SurveyItemResponse.where(survey_item: survey_items, school:).order(updated_at: :DESC).first&.updated_at || Date.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def percentage
|
||||||
|
cap_at_100(actual_count.to_f / respondents_count.to_f * 100).round
|
||||||
|
end
|
||||||
|
|
||||||
|
def color
|
||||||
|
# Problem: the color (either $gold or $purple) is determined by the scss variable, but the
|
||||||
|
# percentage is decided by the presenter. Therefore the class style must be generated
|
||||||
|
# within this file and not the scss file.
|
||||||
|
# TODO: Fix this.
|
||||||
|
percentage > 75 ? '#49416D' : '#FFC857'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def cap_at_100(value)
|
||||||
|
value > 100 ? 100 : value
|
||||||
|
end
|
||||||
|
|
||||||
|
def actual_count
|
||||||
|
SurveyItemResponse.where(school:, academic_year:,
|
||||||
|
survey_item: survey_items).select(:response_id).distinct.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def respondents_count
|
||||||
|
respondents = Respondent.find_by(school:, academic_year:)
|
||||||
|
count = respondents.total_students if focus == :student
|
||||||
|
count = respondents.total_teachers if focus == :teacher
|
||||||
|
count
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<div class="overall-response-rate-container">
|
||||||
|
<div>Response Rates as of <%= response_rate_presenter.date.to_date.strftime("%m/%d/%y") %> </div>
|
||||||
|
<div style="display: flex; justify-content:space-between; width: 100px;">
|
||||||
|
<div><%= response_rate_presenter.focus.capitalize %> </div>
|
||||||
|
<%= render partial: "response_rate_graphic", locals: {response_rate_presenter: response_rate_presenter}, cached: true %>
|
||||||
|
<div><%= response_rate_presenter.percentage %>% </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
<style>
|
||||||
|
/*
|
||||||
|
For some reason, none of the sizing in the pie class works, and it always
|
||||||
|
fills 100% of the containing frame, so the size has to be dictated by .prog
|
||||||
|
*/
|
||||||
|
.prog {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-top: 0.2em;
|
||||||
|
}
|
||||||
|
.pie {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: inline-grid;
|
||||||
|
place-content: center;
|
||||||
|
margin: 5px;
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#response-rate-pie-<%= response_rate_presenter.focus %>:before{
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
inset: 0;
|
||||||
|
background: conic-gradient(<%= response_rate_presenter.color %> calc(<%= response_rate_presenter.percentage %>*1%),#0000 0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="prog">
|
||||||
|
<div id="response-rate-pie-<%= response_rate_presenter.focus %>" class="pie"></div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class AddRecordedDateToSurveyItemResponse < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :survey_item_responses, :recorded_date, :datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ -0,0 +1,160 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ResponseRatePresenter do
|
||||||
|
let(:academic_year) { create(:academic_year, range: '2022-23') }
|
||||||
|
let(:school) { create(:school, name: 'A school') }
|
||||||
|
let(:respondents) { create(:respondent, school:, academic_year:, total_students: 40, total_teachers: 40) }
|
||||||
|
let(:wrong_school) { create(:school, name: 'Wrong school') }
|
||||||
|
let(:wrong_academic_year) { create(:academic_year) }
|
||||||
|
let(:wrong_respondents) do
|
||||||
|
create(:respondent, school: wrong_school, academic_year: wrong_academic_year, total_students: 40,
|
||||||
|
total_teachers: 40)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:student_survey_item) { create(:student_survey_item) }
|
||||||
|
let(:teacher_survey_item) { create(:teacher_survey_item) }
|
||||||
|
let(:oldest_student_survey_response) do
|
||||||
|
create(:survey_item_response, school:, academic_year:, survey_item: student_survey_item)
|
||||||
|
end
|
||||||
|
let(:newest_student_survey_response) do
|
||||||
|
create(:survey_item_response, school:, academic_year:, survey_item: student_survey_item)
|
||||||
|
end
|
||||||
|
let(:oldest_teacher_survey_response) do
|
||||||
|
create(:survey_item_response, school:, academic_year:, survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
let(:newest_teacher_survey_response) do
|
||||||
|
create(:survey_item_response, school:, academic_year:, survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:wrong_student_survey_response) do
|
||||||
|
create(:survey_item_response, school: wrong_school, academic_year: wrong_academic_year,
|
||||||
|
survey_item: student_survey_item)
|
||||||
|
end
|
||||||
|
let(:wrong_teacher_survey_response) do
|
||||||
|
create(:survey_item_response, school: wrong_school, academic_year: wrong_academic_year,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
context '.date' do
|
||||||
|
context 'when focus is student' do
|
||||||
|
before :each do
|
||||||
|
oldest_student_survey_response
|
||||||
|
newest_student_survey_response
|
||||||
|
wrong_student_survey_response
|
||||||
|
wrong_teacher_survey_response
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores all teacher items and only gets the modified date of the last student item' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :student, academic_year:, school:).date
|
||||||
|
expect(rdate).to eq(newest_student_survey_response.updated_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context 'when focus is teacher' do
|
||||||
|
before :each do
|
||||||
|
oldest_teacher_survey_response
|
||||||
|
newest_teacher_survey_response
|
||||||
|
wrong_student_survey_response
|
||||||
|
wrong_teacher_survey_response
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores all student responses and only gets the modified date of the last teacher item' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).date
|
||||||
|
expect(rdate).to eq(newest_teacher_survey_response.updated_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '.percentage' do
|
||||||
|
before :each do
|
||||||
|
respondents
|
||||||
|
wrong_respondents
|
||||||
|
end
|
||||||
|
context 'when no survey responses are found for a school' do
|
||||||
|
it 'returns a response rate of 0' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there all possible teacher respondents answered questions' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 40, school:, academic_year:,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 100' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when more teachers responded than staff the school' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 80, school:, academic_year:,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 100' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when three quarters of the teachers responded to the survey' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 30, school:, academic_year:,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 75' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(75)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context 'when one quarter of the teachers responded to the survey' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 10, school:, academic_year:,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 25' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(25)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context 'When the percentage is not a round number' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 9, school:, academic_year:,
|
||||||
|
survey_item: teacher_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'its rounded to the nearest integer' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :teacher, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(23)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there all possible student respondents answered questions' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 40, school:, academic_year:,
|
||||||
|
survey_item: student_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 100' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :student, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(100)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context 'when half of all students responded' do
|
||||||
|
before :each do
|
||||||
|
create_list(:survey_item_response, 20, school:, academic_year:,
|
||||||
|
survey_item: student_survey_item)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a response rate of 50' do
|
||||||
|
rdate = ResponseRatePresenter.new(focus: :student, academic_year:, school:).percentage
|
||||||
|
expect(rdate).to eq(50)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in new issue