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

Add House, Grade, and Counselor Filters to Discipline Dashboard #2186

Merged

Conversation

edavidsonsawyer
Copy link
Collaborator

@edavidsonsawyer edavidsonsawyer commented Oct 16, 2018

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)

disciplinefilters

Checklists

Which features or pages does this PR touch?

  • Discipline

Does this PR use tests to help verify we can deploy these changes quickly and confidently?

  • Included specs for changes
  • Improved specs for existing code in need of better test coverage
  • Manual testing made more sense here

@kevinrobinson
Copy link
Contributor

kevinrobinson commented Oct 16, 2018

@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:
screen shot 2018-10-16 at 8 48 48 am

@edavidsonsawyer
Copy link
Collaborator Author

@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.

@edavidsonsawyer edavidsonsawyer changed the title Add house filter to discipline dashboard WIP Add house filter to discipline dashboard Oct 16, 2018
@edavidsonsawyer edavidsonsawyer changed the title WIP Add house filter to discipline dashboard Add House, Grade, and Counselor Filters to Discipline Dashboard Oct 18, 2018
@@ -96,7 +96,9 @@ def shared_student_fields(student)
:first_name,
:last_name,
:grade,
:id
:id,
Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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

@edavidsonsawyer edavidsonsawyer changed the title Add House, Grade, and Counselor Filters to Discipline Dashboard WIP Add House, Grade, and Counselor Filters to Discipline Dashboard Oct 18, 2018
@edavidsonsawyer
Copy link
Collaborator Author

@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.

Copy link
Contributor

@kevinrobinson kevinrobinson left a 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', () => {
Copy link
Contributor

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
Copy link
Contributor

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;
Copy link
Contributor

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})?

Copy link
Collaborator Author

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.

Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Collaborator Author

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();
Copy link
Contributor

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.

Copy link
Collaborator Author

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?

Copy link
Contributor

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);
Copy link
Contributor

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

Copy link
Collaborator Author

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?

Copy link
Contributor

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:

image

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.

Copy link
Collaborator Author

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.

Copy link
Contributor

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 🚢 👍

Copy link
Collaborator Author

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!

Copy link
Contributor

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'
};
Copy link
Contributor

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.

Copy link
Collaborator Author

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.

@edavidsonsawyer edavidsonsawyer changed the title WIP Add House, Grade, and Counselor Filters to Discipline Dashboard Add House, Grade, and Counselor Filters to Discipline Dashboard Oct 29, 2018
@kevinrobinson kevinrobinson merged commit e2b739b into studentinsights:master Oct 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants