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

Link enrollment to application in workshop view #33615

Merged
merged 5 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import {Table} from 'react-bootstrap';
import {Button, Table} from 'react-bootstrap';
import ConfirmationDialog from '../../components/confirmation_dialog';
import {enrollmentShape} from '../types';
import {workshopEnrollmentStyles as styles} from '../workshop_enrollment_styles';
Expand All @@ -13,6 +13,7 @@ import {
SubjectNames,
CourseKeyMap
} from '@cdo/apps/generated/pd/sharedWorkshopConstants';
import {CSD, CSP} from '../../application/ApplicationConstants';

const CSF = 'CS Fundamentals';
const DEEP_DIVE = SubjectNames.SUBJECT_CSF_201;
Expand Down Expand Up @@ -141,6 +142,16 @@ export class WorkshopEnrollmentSchoolInfo extends React.Component {
}
}

getApplicationURL(application_id, course) {
if (!application_id || ![CSD, CSP].includes(course)) {
return null;
}

// Note: These paths are defined in ApplicationDashboard component
let path = course === CSD ? 'csd_teachers' : 'csp_teachers';
return `/pd/application_dashboard/${path}/${application_id}`;
}

renderSelectCell(enrollment) {
const checkBoxClass =
this.props.selectedEnrollments.findIndex(e => e.id === enrollment.id) >= 0
Expand Down Expand Up @@ -179,6 +190,11 @@ export class WorkshopEnrollmentSchoolInfo extends React.Component {
);
}

let application_url = this.getApplicationURL(
enrollment.application_id,
this.props.workshopCourse
);

return (
<tr key={i}>
{deleteCell}
Expand All @@ -187,7 +203,16 @@ export class WorkshopEnrollmentSchoolInfo extends React.Component {
<td>{i + 1}</td>
<td>{enrollment.first_name}</td>
<td>{enrollment.last_name}</td>
<td>{enrollment.email}</td>
<td>
{enrollment.email}
{application_url && (
<p>
<Button bsSize="xsmall" href={application_url} target="_blank">
View Application
</Button>
</p>
)}
</td>
<td>{enrollment.district_name}</td>
<td>{enrollment.school}</td>
{this.props.workshopCourse === CSF && (
Expand Down
16 changes: 16 additions & 0 deletions dashboard/app/models/pd/enrollment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,22 @@ def newly_accepted_facilitator?
FACILITATOR_APPLICATION_CLASS.where(user_id: user_id).first&.status == 'accepted'
end

def application_id
find_application_id(user_id, pd_workshop_id)
end

# Finds the application an user used for a workshop.
# Assumes that at most one application like that exists.
# @param [Integer] user_id
# @param [Integer] workshop_id
# @return [Integer, nil] application id or nil if cannot find any application
def find_application_id(user_id, workshop_id)
Pd::Application::ApplicationBase.where(user_id: user_id).each do |application|
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a reason you can't search on user id and application id together?

Copy link
Contributor

@islemaster islemaster Mar 13, 2020

Choose a reason for hiding this comment

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

I think it's because pd_application_id is not a column in our database, but a serialized property inside the JSON properties column for this application record. We can probably use MySQL 5 JSON features for this, but ActiveRecord won't do it for us.

I was wondering if you can use Pd::Application::TeacherApplicationBase here, which provides pd_workshop_id so you don't need the try below. I'm curious to see if/how that modifies the generated query here.

A bigger perf concern, for me, is that using this method and the enrollment serializer is introducing a query-per-enrollment for a workshop page load, which might be a performance issue for very large workshops. We probably don't need to overthink this yet, but let's keep an eye on this feature - if the workshop detail view noticeably slows down, we probably want to pull this logic out to the controller and look up all applications for all enrollments in one query. (Or figure out a change to our data model that makes this less painful.)

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, thinking on this a little more, it does feel like pd_application_id really ought to be a column on enrollment, not a serialized property, with an ActiveRecord association defined.

Copy link
Contributor

Choose a reason for hiding this comment

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

To play devil's advocate, the only time we care about associating an enrollment with an application is in the case of a teacher enrolling in a 5-day (CSP/D) summer workshop, right? That's a really small (albeit important) fraction of the overall number of enrollments, so it'd be like 95% null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Brad's explanation is correct. pd_application_id is a property in property bag, which is saved as a string data type in pd_applications table.

Using TeacherApplicationBase instead of ApplicationBase will not search the entire pd_applications table. It will default to type = TeacherApplicationBase. pd_applications is 1 table shared by multiple models (Single Table Inheritance).

I'll add a JIRA task with potential workarounds Brad and I discussed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bencodeorg: Agree, the number of of enrollments can be mapped to applications is small. The problem is when you open a workshop view, it will grab all enrollments for that workshop in 1 query. However, for each enrollment, we then make 1 more query to pd_applications table to get matching application_id (no matter if it exists or not). If there is 100 enrollments in a workshop, it will create a total of 101 queries instead of 1. This could be a potential performance issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

return application.id if application.try(:pd_workshop_id) == workshop_id
end
nil
end

# Removes the name and email information stored within this Pd::Enrollment.
def clear_data
write_attribute :name, nil
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Api::V1::Pd::WorkshopEnrollmentSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :email, :district_name, :school, :role,
attributes :id, :first_name, :last_name, :email, :application_id, :district_name, :school, :role,
:grades_teaching, :attended_csf_intro_workshop, :csf_course_experience,
:csf_courses_planned, :csf_has_physical_curriculum_guide, :user_id, :attended,
:pre_workshop_survey, :previous_courses, :replace_existing, :attendances,
Expand Down
14 changes: 14 additions & 0 deletions dashboard/test/models/pd/enrollment_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -612,4 +612,18 @@ class Pd::EnrollmentTest < ActiveSupport::TestCase
# initially creates scholarship info with YES_CDO status
assert_equal enrollment.scholarship_status, Pd::ScholarshipInfoConstants::YES_CDO
end

test 'find matching application for an enrollment' do
workshop = create :workshop
teacher = create :teacher
enrollment = create :pd_enrollment, user: teacher, workshop: workshop
application = create :pd_teacher2021_application, user: teacher

assert_nil enrollment.application_id

application.update(pd_workshop_id: workshop.id)

# Can only link an enrollment to an application when the application is assigned to the same workshop
assert_equal application.id, enrollment.application_id
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Api::V1::Pd::WorkshopEnrollmentSerializerTest < ::ActionController::TestCa
test 'serialized workshop enrollment has expected attributes' do
enrollment = create :pd_enrollment
expected_attributes = [
:id, :first_name, :last_name, :email, :district_name, :school, :role,
:id, :first_name, :last_name, :email, :application_id, :district_name, :school, :role,
:grades_teaching, :attended_csf_intro_workshop, :csf_course_experience,
:csf_courses_planned, :csf_has_physical_curriculum_guide, :user_id, :attended,
:pre_workshop_survey, :previous_courses, :replace_existing, :attendances,
Expand Down