Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement import UI #6

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b3d9035
Remove google dependencies (#2)
BenMusch Jun 20, 2022
4c7128c
Merge pull request #1 from BenMusch/ben/rake-scaffolding
BenMusch Jun 20, 2022
c7d1346
Refactor to rely on reach for data (#2)
BenMusch Jul 4, 2022
e8ad968
Add import scripts for users + voters from reach CSVs (#3)
BenMusch Jul 5, 2022
4e15fdb
Actually use twilio
Jul 10, 2022
fd49b91
Fix import
Jul 10, 2022
cecc546
welcome page UI changes + network view new columns
Jul 12, 2022
580490c
fixed voter reg issues
Jul 13, 2022
dfd411b
remove comments + vote status
Jul 15, 2022
380a026
Merge pull request #5 from BenMusch/create-new-data-columns
singhcpt Jul 18, 2022
4ce752d
Expand UI functionality (#6)
singhcpt Aug 6, 2022
6f601f3
Fix key in import script (#7)
BenMusch Aug 6, 2022
41a006f
Link phone number and email (#8)
BenMusch Aug 6, 2022
7d67b2d
Try this ??
Aug 15, 2022
473137e
Another fix
Aug 15, 2022
66b0b1e
ugh
Aug 15, 2022
6eb95fb
Expand survey questions (#9)
singhcpt Aug 20, 2022
1859ba6
UI for uploading Reach CSVs (#10)
BenMusch Aug 20, 2022
944725b
Clean up issue typo (#11)
singhcpt Aug 20, 2022
70d7b8a
All controller endpoints should return a response
Aug 20, 2022
4999669
Implement gotv stuff + survey data bug fix + updated convo guide (#12)
singhcpt Aug 23, 2022
7f9fb61
fix typos
Aug 24, 2022
ed1d819
Merge pull request #13 from BenMusch/fix-typos
singhcpt Aug 24, 2022
8c5dd3e
fix last name bug
Aug 26, 2022
c469613
Merge pull request #14 from BenMusch/fix-naming-bug
singhcpt Aug 26, 2022
cf34c7a
initial dashboard functionality
Aug 31, 2022
bd45b65
admin dashboard
Sep 1, 2022
14bfc06
more admin dashboard features
Sep 1, 2022
e968d84
more admin dashboard stats
Sep 1, 2022
fd02a02
Merge pull request #15 from BenMusch/add-dashboard
singhcpt Sep 1, 2022
ffccf2a
initial functionality
Sep 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/assets/images/nittany_lion_photo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed app/assets/images/ossoff_photo.png
Binary file not shown.
8 changes: 4 additions & 4 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ nav {
max-height: 100%;
}

#ossoff-photo {
#nittany-lion-photo {
/* TODO: resize and optimize photo */
background-image: image-url('ossoff_photo.png');
background-image: image-url('nittany_lion_photo.png');
position: absolute;
bottom: 0;
right: 0;
Expand Down Expand Up @@ -215,7 +215,7 @@ canvas#confetti-canvas {
}

@media only screen and (max-width: 1024px) {
#ossoff-photo {
#nittany-lion-photo {
width: 40%;
}
.hero #user-name {
Expand All @@ -224,7 +224,7 @@ canvas#confetti-canvas {
}

@media only screen and (max-width: 768px) {
#ossoff-photo {
#nittany-lion-photo {
display: none;
}
form#login-form .control.phone-number, form#login-form .input {
Expand Down
44 changes: 44 additions & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'json'

class AdminController < ApplicationController
helper_method :get_network_size_by_user

def show
current_user.log_voter!(params[:id].to_s)
@voter = Voter.find(params[:id])
end

def show_dashboard
@users = User.all
@user_set = User.first(3)
@voters = Voter.all
@relationships = Relationship.all

@confirmed_registrations = @voters.select {|v| v.voter_registration_status_display == "Registered in PA HD 82"}.count
@pending_registrations = @voters.select {|v| v.voter_registration_status_display == "Pending registration in PA HD 82"}.count
@unregistered = @voters.select {|v| v.voter_registration_status_display != "Pending registration in PA HD 82" && v.voter_registration_status_display != "Registered in PA HD 82"}.count
@total_unofficial_registrations = @pending_registrations + @confirmed_registrations

cares_climate = {"Climate" => @voters.select { |v| v.safe_survey_data["issues"]["cares_climate"] == "1"}.count}
cares_gun_control = {"Gun control" => @voters.select { |v| v.safe_survey_data["issues"]["cares_gun_control"] == "1"}.count}
cares_healthcare = {"Healthcare" => @voters.select { |v| v.safe_survey_data["issues"]["cares_healthcare"] == "1"}.count}
cares_college_affordability = {"College affordability" => @voters.select { |v| v.safe_survey_data["issues"]["cares_about_college_affordability"] == "1"}.count}
cares_reproductive_rights = {"Reproductive rights" => @voters.select { |v| v.safe_survey_data["issues"]["cares_reproductive_rights"] == "1"}.count}
cares_transparency = {"Accountability/transparency of government officials" => @voters.select { |v| v.safe_survey_data["issues"]["cares_transparency"] == "1"}.count}
cares_marijuana = {"Marijuana legalization" => @voters.select { |v| v.safe_survey_data["issues"]["cares_marijuana"] == "1"}.count}
cares_gender_equity = {"Gender equity/Title IX" => @voters.select { |v| v.safe_survey_data["issues"]["cares_gender_equity"] == "1"}.count}
cares_pay_gap = {"Gender pay gap" => @voters.select { |v| v.safe_survey_data["issues"]["cares_pay_gap"] == "1"}.count}
cares_sexual_assault = {"Sexual assault/violence" => @voters.select { |v| v.safe_survey_data["issues"]["cares_sexual_assault"] == "1"}.count}
plan_to_vote_before = @voters.select { |v| v.safe_survey_data["plan_to_vote_before"] == "1"}.count
plan_to_vote_for_paul = @voters.select { |v| v.safe_survey_data["plan_to_vote_for_paul"] == "1"}.count

survey_data = [cares_climate, cares_gun_control, cares_healthcare, cares_college_affordability, cares_reproductive_rights, cares_transparency, cares_marijuana, cares_gender_equity, cares_sexual_assault]
@sorted_survey_data = survey_data.sort_by { |issue| issue.values.first }.reverse
end

def get_network_size_by_user
return
end

end
63 changes: 63 additions & 0 deletions app/controllers/imports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
class ImportsController < ApplicationController
before_action :require_admin

def index
end

def create
successful_uploads = []
failed_uploads = []

loader = ReachCsvLoader.new

if params[:users_file]
was_success = !loader.load_users(from: params[:users_file].tempfile)
if was_success
successful_uploads += ["Users"]
else
failed_uploads += ["Users"]
end
end

if params[:voters_file]
was_success = !loader.load_voters(from: params[:voters_file].tempfile)
if was_success
successful_uploads += ["Voters"]
else
failed_uploads += ["Voters"]
end
end

if params[:relationships_file]
was_success = !loader.load_relationships(from: params[:relationships_file].tempfile)
if was_success
successful_uploads += ["Relationships"]
else
failed_uploads += ["Relationships"]
end
end

if (successful_uploads + failed_uploads).empty?
flash[:warning] = "No uploads processed"
end

if failed_uploads.any?
flash[:danger] = "Got errors handing import of: #{failed_uploads.join(", ")}. Please check the app logs."
end

if successful_uploads.any?
flash[:success] = "Successfully imported: #{successful_uploads.join(", ")}"
end

redirect_to imports_path
end

private

def require_admin
unless current_user.is_admin
flash[:danger] = 'You are not authorized to view that page'
redirect_to relationships_path
end
end
end
11 changes: 2 additions & 9 deletions app/controllers/relationships_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
class RelationshipsController < ApplicationController
def index
# to_a calls
@contacts = current_user.non_self_voters.order(:tier, :sos_id).load
@secondary_contacts = current_user.secondary_network.order(:tier, :sos_id).load

@contacted_size = (@contacts + @secondary_contacts).count { |v| !v.not_yet_called? }
@tier_4_size = (@contacts + @secondary_contacts).count { |v| v.tier == 4 }

@contacts = @contacts.reject { |v| v.tier == 4 }
@secondary_contacts = @secondary_contacts.reject { |v| v.tier == 4 }
@contacts = current_user.non_self_voters.order(:reach_id).load
@contacted_size = (@contacts).count { |v| !v.not_yet_called? }
end
end
15 changes: 8 additions & 7 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ def set_client
end

def start_verification(to, channel='sms')
return # unless Rails.env.production?
channel = 'sms' unless ['sms', 'voice'].include? channel
verification = @client.verify.services(ENV['VERIFICATION_SID'])
.verifications
.create(:to => '+1' + to, :channel => channel)
verification.sid
unless Rails.env.development?
channel = 'sms' unless ['sms', 'voice'].include? channel
verification = @client.verify.services(ENV['VERIFICATION_SID'])
.verifications
.create(:to => '+1' + to, :channel => channel)
verification.sid
end
end

def check_verification(phone, code)
return true # unless Rails.env.production?
return true if Rails.env.development?
begin
verification_check = @client.verify.services(ENV['VERIFICATION_SID'])
.verification_checks
Expand Down
83 changes: 51 additions & 32 deletions app/controllers/voter_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true
require 'json'

class VoterController < ApplicationController
SKIP_WARN_THRESHOLDS = Set.new(Rails.configuration.rewards.skip_warnings)
Expand All @@ -14,7 +15,7 @@ def show
def next
call_list = current_user.call_list
voters_seen = current_user.seen_voters
next_voter = call_list.find { |v| !voters_seen[v.sos_id.to_s] }
next_voter = call_list.find { |v| !voters_seen[v.reach_id] }

if params[:skip] && voter
record_in_reach(Rails.configuration.reach.responses[:skip])
Expand All @@ -41,36 +42,63 @@ def next
end

def update
if params[:last_call_status]
if voter_params[:last_call_status]
current_user.log_call!
if voter.update(last_call_status: params[:last_call_status])
record_in_reach(Rails.configuration.reach.responses.to_h.fetch(params[:last_call_status].to_sym))
calls_logged = current_user.calls_logged
if Rails.configuration.rewards.videos.key?(calls_logged.to_s)
flash[:success] = Rails.configuration.rewards.messages[:success]
flash[:confetti] = true
flash[:video] = REWARD_VIDEOS[calls_logged]
elsif calls_logged == 1
flash[:success] = Rails.configuration.rewards.messages[:first]
flash[:confetti] = true
else
flash[:success] = 'Call status updated, check out the next voter to call!'
end
# TODO: un-comment this if we want to sync responses to the Reach API
# record_in_reach(Rails.configuration.reach.responses.to_h.fetch(params[:last_call_status].to_sym))
end

if voter.update(voter_params)
# if last_call_status is what changed, we want to redirect to the next
# voter. Else we just want to reload the previous voter
if voter_params[:last_call_status]
flash[:success] = 'Contact status updated, check out the next voter to call!'
redirect_to voter_next_path
else
flash[:danger] = 'Error updating call status, try clicking again!'
redirect_to voter_next_path
flash[:success] = 'Changes saved successfully'
redirect_to @voter
end
elsif params[:is_needs_a_ride_form]
needs_a_ride = params[:needs_a_ride] == "1"
record_in_reach(Rails.configuration.reach.responses[:needs_a_ride]) if needs_a_ride
voter.update(needs_a_ride: needs_a_ride)
else
flash[:danger] = 'Error recording changes, try again!'
redirect_to @voter
end
end

def update_survey
survey_data_from_form =
{
"issues" => {
"cares_climate" => params[:cares_about_climate].present? ? "1" : "0",
"cares_gun_control" => params[:cares_about_gun_control].present? ? "1" : "0",
"cares_healthcare" => params[:cares_about_healthcare].present? ? "1" : "0",
"cares_college_affordability" => params[:cares_about_college_affordability].present? ? "1" : "0",
"cares_reproductive_rights" => params[:cares_about_reproductive_rights].present? ? "1" : "0",
"cares_transparency" => params[:cares_about_transparency].present? ? "1" : "0",
"cares_marijuana" => params[:cares_about_marijuana].present? ? "1" : "0",
"cares_gender_equity" => params[:cares_about_gender_equity].present? ? "1" : "0",
"cares_pay_gap" => params[:cares_about_gender_pay_gap].present? ? "1" : "0",
"cares_sexual_assault" => params[:cares_about_sexual_assault].present? ? "1" : "0"
},
"plan_to_vote_before" => params[:planned_to_vote_before].present? ? "1" : "0",
"plan_to_vote_for_paul" => params[:planned_to_vote_for_paul].present? ? "1" : "0"
}

voter = Voter.find(params[:id])
if voter.update(survey_data: survey_data_from_form.to_json)
flash[:success] = 'Survey data updated'
else
flash[:danger] = 'Error updating survey responses, try again'
end

redirect_to voter
end

private

def voter_params
params.require(:voter).permit(:email, :last_call_status, :voter_registration_status, :notes, :party_id, :vote_plan, :vote_status)
end

def migrate_voters_seen
if current_user && session[:voters_seen].is_a?(Hash)
current_user.set_voters!(session[:voters_seen])
Expand All @@ -84,17 +112,8 @@ def voter

def authorize_and_set_contact
return unless voter.relationships.where(user: current_user).empty?
same_household_voter = voter.
household_members.
where(sos_id: current_user.relationships.select(:voter_sos_id)).
first

if same_household_voter
@same_household_voter = same_household_voter
else
flash[:danger] = "You don't have access to that voter's details"
redirect_back(fallback_location: root_path)
end
flash[:danger] = "You don't have access to that voter's details"
redirect_back(fallback_location: root_path)
end

def record_in_reach(choice_id)
Expand Down
40 changes: 40 additions & 0 deletions app/lib/pa_sos_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

# Load data from the tab-separated PA secretary of state data
class PaSosLoader
VOTER_FILE_LOCATION = ENV['PA_SOS_VOTER_FILE']

def load_voters
to_upsert = []

# PA SOS data is a tab-separated file with the format
# StateVoterID/unknown/LastName/FirstName/MiddleName/Suffix/Gender/DOB/DateRegistered/ActiveorInactive/Not Sure what this date is--might be updated reg?/PartyRegistration/House Number/Apt/StreetName/City/State/Zip/
data = File.readlines(VOTER_FILE_LOCATION)
data.each do |line|
sos_id, _, last_name, first_name, middle_name, _suffix, gender, _dob,\
_date_registered, _is_active, _, _party, house_number, \
apartment_number, street_name, _, _, city, state, zip = \
line.split("\t").
map { |col_data| col_data.start_with?("\"") ? col_data[1...-1] : col_data } # strip surrounding quotes

voting_street_address = "#{house_number} #{street_name}"
if apartment_number.present?
voting_street_address += " ##{apartment_number}"
end

voter_data = {
sos_id: sos_id,
first_name: first_name,
last_name: last_name,
middle_name: middle_name,
gender: gender,
voting_city: city,
voting_zip: zip,
voting_street_address: voting_street_address,
}
to_upsert << voter_data
end

Voter.upsert_all(to_upsert, unique_by: nil)
end
end
Loading