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

Facilitator application csv #26723

Merged
merged 19 commits into from
Jan 25, 2019
24 changes: 4 additions & 20 deletions dashboard/app/controllers/api/v1/pd/applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,8 @@ def quick_view
render json: serialized_applications
end
format.csv do
if [:csd_teachers, :csp_teachers].include? role
csv_text = get_csv_text applications, role
send_csv_attachment csv_text, "#{role}_applications.csv"
else
prefetch applications, role: role
course = role[0..2] # course is the first 3 characters in role, e.g. 'csf'
csv_text = [
TYPES_BY_ROLE[role].csv_header(course, current_user),
*applications.map {|a| a.to_csv_row(current_user)}
].join
send_csv_attachment csv_text, "#{role}_applications.csv"
end
csv_text = get_csv_text applications, role
send_csv_attachment csv_text, "#{role}_applications.csv"
end
end
end
Expand Down Expand Up @@ -124,14 +114,8 @@ def cohort_view
render json: serialized_applications
end
format.csv do
if [:csd_teachers, :csp_teachers].include? role.to_sym
csv_text = get_csv_text applications, role
send_csv_attachment csv_text, "#{role}_cohort_applications.csv"
else
optional_columns = get_optional_columns(regional_partner_value)
csv_text = [TYPES_BY_ROLE[role.to_sym].cohort_csv_header(optional_columns), applications.map {|app| app.to_cohort_csv_row(optional_columns)}].join
send_csv_attachment csv_text, "#{role}_cohort_applications.csv"
end
csv_text = get_csv_text applications, role
send_csv_attachment csv_text, "#{role}_cohort_applications.csv"
end
end
end
Expand Down
13 changes: 8 additions & 5 deletions dashboard/app/models/pd/application/application_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,17 +242,16 @@ def additional_text_fields

# Override in derived class to provide headers
# @param course [String] course name used to choose fields, since they differ between courses
# @param user [User] requesting user - used to handle field visibility differences
# @return [String] csv text row of column headers, ending in a newline
def self.csv_header(course, user)
def self.csv_header(course)
raise 'Abstract method must be overridden by inheriting class'
end

# Override in derived class to provide the relevant csv data
# @param user [User] requesting user - used to handle field visibility differences
# @param course [String] course name used to choose fields, since they differ between courses
# @return [String] csv text row of values, ending in a newline
# The order of fields must be consistent between this and #self.csv_header
def to_csv_row(user)
def to_csv_row(course)
raise 'Abstract method must be overridden by inheriting class'
end

Expand Down Expand Up @@ -369,7 +368,11 @@ def course_name

# displays the iso8601 date (yyyy-mm-dd)
def date_accepted
accepted_at.try {|datetime| datetime.to_date.iso8601}
accepted_at&.to_date&.iso8601
end

def date_applied
created_at.to_date.iso8601
end

# Convert responses cores to a hash of underscore_cased symbols
Expand Down
151 changes: 32 additions & 119 deletions dashboard/app/models/pd/application/facilitator1920_application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,138 +148,51 @@ def self.filtered_labels(course)
FILTERED_LABELS[course]
end

# @override
def self.csv_header(course, user)
# strip all markdown formatting out of the labels
markdown = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
CSV.generate do |csv|
columns = filtered_labels(course).values.map {|l| markdown.render(l)}.map(&:strip)
columns.push(
'Status',
'Locked',
'General Notes',
'Notes 2',
'Notes 3',
'Notes 4',
'Notes 5',
'Question 1 Support Teachers',
'Question 2 Student Access',
'Question 3 Receive Feedback',
'Question 4 Give Feedback',
'Question 5 Redirect Conversation',
'Question 6 Time Commitment',
'Question 7 Regional Needs',
'Regional Partner'
)
csv << columns
# Filter out extraneous answers based on selected program (course)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function creates a list of columns to filter, correct? If so, can you update this column to a bit clearer?

def self.columns_to_remove(course)
if course == 'csf'
CSV_LABELS.keys.select {|k| k.to_s.start_with?('csd', 'csp')}
elsif course == 'csd'
CSV_LABELS.keys.select {|k| k.to_s.start_with?('csf', 'csp')}
else
CSV_LABELS.keys.select {|k| k.to_s.start_with?('csf', 'csd_training')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why csd_training here but just csd above?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many questions are common to csd and csp, and they are all prefixed with 'csd_csp'. So for a csp csv, we want to include all of those. The only csd-specific question is the one prefixed 'csd_training', so we want to exclude that one here.

The course names, as stored in the course column, are 'csf', 'csd', or 'csp'. I should probably be using the constants for these.

end
end

def self.cohort_csv_header(optional_columns)
columns = [
'Date Accepted',
'Name',
'School District',
'School Name',
'Email',
'Status',
'Assigned Workshop'
]
if optional_columns[:registered_workshop]
columns.push 'Registered Workshop'
end
if optional_columns[:accepted_teachercon]
columns.push 'Accepted Teachercon'
end

columns.push(
'General Notes',
'Notes 2',
'Notes 3',
'Notes 4',
'Notes 5',
'Question 1 Support Teachers',
'Question 2 Student Access',
'Question 3 Receive Feedback',
'Question 4 Give Feedback',
'Question 5 Redirect Conversation',
'Question 6 Time Commitment',
'Question 7 Regional Needs'
)
def self.csv_filtered_labels(course)
labels = {}
labels_to_remove = Pd::Application::Facilitator1920Application.columns_to_remove(course)

CSV.generate do |csv|
csv << columns
CSV_LABELS.keys.each do |k|
unless labels_to_remove.include? k.to_sym
labels[k] = CSV_LABELS[k]
end
end
labels
end

# @override
def to_csv_row(user)
answers = full_answers
def self.csv_header(course)
# strip all markdown formatting out of the labels
markdown = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
CSV.generate do |csv|
row = self.class.filtered_labels(course).keys.map {|k| answers[k]}
row.push(
status,
locked?,
notes,
notes_2,
notes_3,
notes_4,
notes_5,
question_1,
question_2,
question_3,
question_4,
question_5,
question_6,
question_7,
regional_partner_name
)
csv << row
columns = csv_filtered_labels(course).values.map {|l| markdown.render(l)}.map(&:strip)
csv << columns
end
end

def to_cohort_csv_row(optional_columns)
columns = [
date_accepted,
applicant_name,
district_name,
school_name,
user.email,
status,
fit_workshop_date_and_location
]
if optional_columns[:registered_workshop]
if workshop.try(:local_summer?)
columns.push(registered_workshop? ? 'Yes' : 'No')
else
columns.push nil
end
end
if optional_columns[:accepted_teachercon]
if workshop.try(:teachercon?)
columns.push(pd_teachercon1819_registration ? 'Yes' : 'No')
else
columns.push nil
end
end

columns.push(
notes,
notes_2,
notes_3,
notes_4,
notes_5,
question_1,
question_2,
question_3,
question_4,
question_5,
question_6,
question_7
)

# @override
def to_csv_row(course)
columns_to_exclude = Pd::Application::Facilitator1920Application.columns_to_remove(course)
CSV.generate do |csv|
csv << columns
row = []
CSV_LABELS.keys.each do |k|
if columns_to_exclude&.include? k.to_sym
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense for the columns_to_remove method to never return nil so the safe-navigation operator isn't necessary here? It seems to me like an empty collection is a reasonable default case.

next
end
row.push(full_answers[k] || try(k) || all_scores[k])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nits (with the caveat that I'm not the most fluent rubyist):

I feel like I've been pushed toward inlining one-line conditionals like this one:

next if columns_to_exclude&.include? k.to_sym

Then again, I wonder if it's simpler change if A then next else B to if !A then B:

unless columns_to_exclude&.include? k.to_sym
  row.push(full_answers[k] || try(k) || all_scores[k])
end

Or (and I'm not sure this is cleaner) can we just filter the set we're iterating over?

CSV_LABELS.keys.
  reject {|k| columns_to_exclude&.include? k.to_sym}.
  each {|k| row.push(full_answers[k] || try(k) || all_scores[k]}

I don't want to be prescriptive here - totally open to conversation about what we find most readable.

end
csv << row
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ def fit_workshop_date_and_location
fit_workshop.try(&:date_and_location_name)
end

def assigned_workshop_date_and_location
Pd::Workshop.find_by(id: pd_workshop_id)&.date_and_location_name
end

def log_fit_workshop_change(user)
update_status_timestamp_change_log(user, "Fit Workshop: #{fit_workshop_id ? fit_workshop_date_and_location : 'Unassigned'}")
end
Expand All @@ -105,6 +109,14 @@ def registered_fit_workshop?
fit_workshop.enrollments.any? {|e| e.user_id == user.id} if fit_workshop_id
end

def friendly_registered_workshop(workshop_id = pd_workshop_id)
Pd::Enrollment.find_by(user: user, workshop: workshop_id) ? 'Yes' : 'No'
end

def friendly_registered_fit_workshop
friendly_registered_workshop(fit_workshop_id)
end

def self.options
{
title: COMMON_OPTIONS[:title],
Expand Down Expand Up @@ -395,6 +407,10 @@ def destroy_fit_autoenrollment
self.auto_assigned_fit_enrollment_id = nil
end

def application_url
CDO.studio_url("/pd/application_dashboard/#{course}_facilitators/#{id}", CDO.default_scheme)
end

# override
def enroll_user
super
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,6 @@ def check_idempotency
Teacher1920Application.find_by(user: user)
end

def date_applied
created_at.to_date.iso8601
end

def date_accepted
accepted_at&.to_date&.iso8601
end

def assigned_workshop
Pd::Workshop.find_by(id: pd_workshop_id)&.date_and_location_name
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -993,25 +993,87 @@ class ApplicationsControllerTest < ::ActionController::TestCase
response_csv = CSV.parse @response.body

expected_headers = [
'Date Applied',
'Date Accepted',
'Name',
'School District',
'School Name',
'Email',
'Status',
'Assigned Workshop',
'General Notes',
'Notes 2',
'Notes 3',
'Notes 4',
'Notes 5',
'Question 1 Support Teachers',
'Question 2 Student Access',
'Question 3 Receive Feedback',
'Question 4 Give Feedback',
'Question 5 Redirect Conversation',
'Question 6 Time Commitment',
'Question 7 Regional Needs'
'Locked',
'Meets Minimum Requirements?',
'Teaching Experience Score',
'Leadership Score',
'Champion for CS Score',
'Equity Score',
'Growth Minded Score',
'Content Knowledge Score',
'Program Commitment Score',
'Application Total Score',
'Interview Total Score',
'Grand Total Score',
"General Notes",
"Notes 2",
"Notes 3",
"Notes 4",
"Notes 5",
"Title",
"First Name",
"Last Name",
"Account Email",
"Alternate Email",
"Home or Cell Phone",
"Home Address",
"City",
"State",
"Zip Code",
"Gender Identity",
"Race",
'Assigned Summer Workshop',
'Registered Summer Workshop?',
'Assigned FiT Workshop',
'Registered FiT Workshop?',
'Regional Partner',
'Link to Application',
'What type of institution do you work for?',
'Current employer',
'What is your job title?',
'Program',
'Are you currently (or have you been) a Code.org facilitator?',
'In which years did you work as a Code.org facilitator?',
'Please check the Code.org programs you currently facilitate, or have facilitated in the past:',
'Do you have experience as a classroom teacher?',
'Have you led learning experiences for adults?',
'Can you commit to attending the 2019 Facilitator Summit (May 17 - 19, 2019)?',
'Can you commit to facilitating a minimum of 4-6 one-day workshops starting summer 2019 and continuing throughout the 2019-2020 school year?',
'Can you commit to attending monthly webinars, or watching recordings, and staying up to date through bi-weekly newsletters and online facilitator communities?',
'Can you commit to engaging in appropriate development and preparation to be ready to lead workshops (time commitment will vary depending on experience with the curriculum and experience as a facilitator)?',
'Can you commit to remaining in good standing with Code.org and your assigned Regional Partner?',
'How are you currently involved in CS education?',
'If you do have classroom teaching experience, what grade levels have you taught? Check all that apply.',
'Do you have experience teaching the full {{CS Program}} curriculum to students?',
'Do you plan on teaching this course in the 2019-20 school year?',
'Have you attended a Code.org CS Fundamentals workshop?',
'When do you anticipate being able to facilitate? Note that depending on the program, workshops may be hosted on Saturdays or Sundays.',
Pd::Facilitator1920ApplicationConstants.clean_multiline(
"Code.org's Professional Learning Programs are open to all teachers, regardless of their experience with CS education.
Why do you think Code.org believes that all teachers should have access to the opportunity to teach CS?"
),
Pd::Facilitator1920ApplicationConstants.clean_multiline(
"Please describe a workshop you've led (or a lesson you've taught, if you haven't facilitated a workshop). Include a brief description of the workshop/lesson
topic and audience (one or two sentences). Then describe two strengths you demonstrated, as well as two facilitation skills you would like to improve.",
),
Pd::Facilitator1920ApplicationConstants.clean_multiline(
"Code.org Professional Learning experiences incorporate inquiry-based learning into the workshops. Please briefly define inquiry-based
learning as you understand it (one or two sentences). Then, if you have led an inquiry-based activity for students, provide a concrete
example of an inquiry-based lesson or activity you led. If you have not led an inquiry-based lesson, please write 'N/A.'",
),
'Why do you want to become a Code.org facilitator? Please describe what you hope to learn and the impact you hope to make.',
'Is there anything else you would like us to know? You can provide a link to your resume, LinkedIn profile, website, or summarize your relevant past experience.',
'How did you hear about this opportunity?',
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_1]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_2]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_3]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_4]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_5]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_6]}",
"Interview #{Pd::Facilitator1920ApplicationConstants::INTERVIEW_QUESTIONS[:question_7]}"
]
assert_equal expected_headers, response_csv.first
assert_equal expected_headers.length, response_csv.second.length
Expand Down