-
Notifications
You must be signed in to change notification settings - Fork 54
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
Add House, Grade, and Counselor Filters to Discipline Dashboard #2186
Add House, Grade, and Counselor Filters to Discipline Dashboard #2186
Conversation
@edavidsonsawyer The tests are failing here, and the checklists in the pull request description aren't finished, so I didn't review this, but let me know when I should! re: design, in #1661 (comment) the issue is to add filters for grade, house and counselor. If you want to ship those incrementally, that's great! If shipping incrementally, then the order matters so we don't reorder the filters on folks with each change we ship :) You can use the absences dashboard as a model here, with the specific bits (eg, unexcused / incident type) coming first, and then the same other filters coming after in the same order: |
@kevinrobinson yeah, this and the other PR are still WIP, I'll ping you when they're all set. I hadn't thought about reordering the filters - I was trying to get this quickly useful for high school educators, but it shouldn't be a problem to add all filters to this PR. |
@@ -96,7 +96,9 @@ def shared_student_fields(student) | |||
:first_name, | |||
:last_name, | |||
:grade, | |||
:id | |||
:id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrobinson I wanted to call this out since it also means sending that info to the tardies dashboard. My thinking is eventually we'll want to add these filters there as well, and it doesn't do any harm, but if we want to keep this compartmentalized then this can be done specifically for the data sent in the discipline api.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrobinson ignore this for now, though, still some work to be done on the tests before this all gets reviewed
@kevinrobinson All right, I've tested out these filters with and without large incident counts. They all look like they work and appear when needed based on the school. When you have the chance, I'd appreciate a code review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edavidsonsawyer This looks great! I reviewed this based on your comment, but please also update the pull request description to match too. :) And sorry on the delay here, I was out a bit this week and probably should have sent you a note about that.
This seems good, most of the comments here are minor style suggestions. The most meaningful bit for me is about how the "grade filter" looks like it's currently stored in two separate bits of state, depending on whether the user used the select box, or clicked on a chart. I wonder if there's an opportunity to simplify that to just one bit of state?
)); | ||
} | ||
|
||
it('displays house for SHS', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice tests! 👍
|
||
jest.mock('react-virtualized'); // doesn't work in test without a real DOM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW if you want to use snapshot tests on something that uses react-virtualized
you can do that using a similar approach to the one in #2196. It's fine to not do that here, just sharing about a new way to approach this that I was trying.
if (house !== ALL && student.house !== house) return false; | ||
if (counselor !== ALL && student.counselor !== counselor) return false; | ||
if (selectedCategory && shouldFilterSelectedCategory) { //used by the student list when a user selects a category within a chart | ||
if (selectedChart === 'grade' && student.grade !== selectedCategory) return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like there are two separate bits of state related to the grade filtering. One is for if the user uses the select filter, and the other is if they click on a grade within a chart.
Could these be a single bit of state? In other words, when the user clicks on a grade within a chart, would it simplify to call this.setState({grade})
or to call this.setsState({homeroom})
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could certainly do that. It might be awkward to treat selecting a grade within a chart different from selecting other categories within a chart. We would still have need state for selectedCategory, but have an exception when that category is a grade. Since there are two ways you might filter a grade and the application behavior is different depending on which is selected, it seemed to make sense to hold that as two different bits of state.
I imagine moving forward that we might not need the grade/homeroom charts with the top level filters, in which case this problem should go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is super helpful! 👍 Sounds good to just keep them distinct then and 🚢 this.
@@ -187,7 +204,7 @@ export default class SchoolDisciplineDashboard extends React.Component { | |||
} | |||
|
|||
studentTableRows(students) { | |||
const filteredStudents = this.filterStudents(students); | |||
const filteredStudents = this.filterStudents(students, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you revise the argument to be options = {}
this can help make it more obvious what's happening at the call site. Right now, reading this line of code, there's no indication of what true
means.
Using the options = {}
argument let's you write code like this at the call site:
this.filterStudents(students, { shouldFilterSelectedCategory: true })
and express explicitly what you want that function to be doing. I'd recommend this as a good rule of thumb in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this! I was sure there was a better syntax I was missing.
allIncidentTypes() { | ||
const {dashboardStudents} = this.props; | ||
const incidents = _.uniqBy(_.compact(_.flatten(dashboardStudents.map(student => student.discipline_incidents))), 'incident_code'); | ||
return incidents.map(incident => incident.incident_code).sort(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a small style suggestions, if this is just trying to get all the codes, maybe flat map then uniq then sort (like you do in the other methods)? It doesn't seem like the whole object of incidents
is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an attempt to make it more readable, since this one has to iterate through students and then incidents. Are you saying it would be preferable to have it all on one line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah trying to make things more readable is great! My comment is a tiny one, but more about what's happening here logically. This is what I see in the code:
- first, it grabs the incidents from each student and flattens that into an array
- then it runs compact, which I think won't do anything (it shouldn't be possible for nil values to be here)
- then it uniques by
incident_code
to get the list of incidents - then it maps the incidents down to just
incident_code
and sorts
So it looks to me like compact
isn't doing anything here, and that if the map / uniq were swapped, then that would allow removing the repetition of doing something by incident_code
twice in two lines. Concretely, it's sort of the difference between these two lines:
_.uniq(_.map(incidents, 'incident_code'))
_.map(_.uniqBy(incidents, 'incident_code'), 'incident_code')
Also this is a super tiny thing, please just go with what you think is most readable and best, we don't have to keep 🚲 shedding it, and also it's fine if you disagree :)
const props = {school: testSchool}; | ||
const el = testEl(props); | ||
const dash = mount(elWrappedInContext(el, context)); | ||
expect(dash.find('SelectTimeRange').length > 0).toEqual(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this code now works differently per district, it would be good to add at least smoke tests for that. Here's how this works in the absences page: https://github.com/studentinsights/studentinsights/blob/master/app/assets/javascripts/school_absences/SchoolAbsencesDashboard.test.js#L211
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrobinson I've got most of your suggestions implemented, but the CI build is hassling me about these snapshots. Is there something I should be sending in this branch to be able to write snapshots?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edavidsonsawyer Yeah! So with snapshots, you need to update this locally and push up the changes.
Conceptually, snapshots are the same as any other test - your PR has to include how the expectations for the tests are different. The only wrinkle is that Jest makes the snapshot expectations for you. It should prompt you to do this when you run yarn test
locally, something like this:
Here's a direct link to the Jest docs about this: https://jestjs.io/docs/en/snapshot-testing.html#updating-snapshots or if you our setup isn't working quite right or is confusing, let me know and I can take a look with you so we can make this work nicely and remove any friction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrobinson got it now, sorry that took so long. I forgot to merge master into this branch and it was messing up the snapshots here, but not locally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@edavidsonsawyer no worries! Is this ready to change from WIP and to deploy now? I can do that later today if this is ready to 🚢 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kevinrobinson sure is! thanks again for the review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no problemo!
export const testSchool = { | ||
local_id: 'HEA', | ||
name: 'Arthur D. Healey' | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you say more about changing these from functions to shared global objects? One benefit of functions is that there's no possibility of a test polluting the value, since you're creating a new test object each time. If it's a global object, it opens up a class of bugs where test code or real code could update the object and break completely unrelated tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you're right. I was thinking of this as static fixture data, but there's no reason to switch while it's still a shared component.
Who is this PR for?
Discipline dashboard users
What problem does this PR fix?
To be useful in high schools, educators need to be able to look at discipline incidents by house, grade, and counselor.
What does this PR do?
Adds a top level house filter for schools that support it. cf #1661
Screenshot (if adding a client-side feature)
Checklists
Which features or pages does this PR touch?
Does this PR use tests to help verify we can deploy these changes quickly and confidently?