Skip to content

Commit 51edbf2

Browse files
Bruce-8mimischly7david-yz-liu
authored
Allow inactive groups in the graders table to be toggled for display (#6778)
Co-authored-by: Mimis Chlympatsos <[email protected]> Co-authored-by: David Liu <[email protected]>
1 parent 738573d commit 51edbf2

7 files changed

Lines changed: 244 additions & 7 deletions

File tree

.github/workflows/test_ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ jobs:
8585
run: bundle exec rails db:migrate
8686
- name: Install chrome and chromedriver
8787
uses: nanasess/setup-chromedriver@v2
88+
with:
89+
chromedriver-version: '119.0.6045.199'
8890
- name: Run chromedriver
8991
run: chromedriver --whitelisted-ips &
9092
- name: Run rspec tests

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## [unreleased]
4+
- Allow inactive groups in the graders table to be toggled for display (#6778)
45
- Enable plotly rendering for jupyter notebooks (#6871)
56

67
## [v2.4.1]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/***
2+
* Tests for GradersManager Component
3+
*/
4+
5+
import {GradersManager} from "../graders_manager";
6+
import {render, screen, fireEvent} from "@testing-library/react";
7+
8+
jest.mock("@fortawesome/react-fontawesome", () => ({
9+
FontAwesomeIcon: () => {
10+
return null;
11+
},
12+
}));
13+
14+
describe("For the GradersManager's display of inactive groups", () => {
15+
let groups_sample;
16+
beforeEach(() => {
17+
groups_sample = [
18+
{
19+
_id: 15,
20+
members: [
21+
["c9nielse", "inviter", true],
22+
["c8szyman", "accepted", true],
23+
],
24+
inactive: false,
25+
grace_credits: 5,
26+
remaining_grace_credits: 4,
27+
group_name: "group_0015",
28+
graders: [],
29+
criteria_coverage_count: 0,
30+
},
31+
{
32+
_id: 15,
33+
members: [
34+
["d2lifese", "inviter", false],
35+
["a3kjcbod", "accepted", false],
36+
],
37+
inactive: false,
38+
grace_credits: 5,
39+
remaining_grace_credits: 4,
40+
group_name: "group_0014",
41+
graders: [],
42+
criteria_coverage_count: 0,
43+
},
44+
];
45+
fetch.mockReset();
46+
fetch.mockResolvedValueOnce({
47+
ok: true,
48+
json: jest.fn().mockResolvedValueOnce({
49+
graders: [],
50+
criteria: [],
51+
assign_graders_to_criteria: false,
52+
loading: false,
53+
sections: {},
54+
anonymize_groups: false,
55+
hide_unassigned_criteria: false,
56+
isGraderDistributionModalOpen: false,
57+
groups: groups_sample,
58+
}),
59+
});
60+
render(<GradersManager sections={{}} course_id={1} assignment_id={1} />);
61+
});
62+
63+
it("contains the correct amount of inactive groups in the hidden tooltip", () => {
64+
expect(screen.getByTestId("show_hidden_groups_tooltip").getAttribute("title")).toEqual(
65+
"1 inactive group"
66+
);
67+
});
68+
69+
it("initially contains the active group", () => {
70+
expect(screen.getByText("group_0014")).toBeInTheDocument();
71+
});
72+
73+
it("contains the inactive group after a single toggle", () => {
74+
fireEvent.click(screen.getByTestId("show_hidden_groups"));
75+
expect(screen.getByText("group_0015")).toBeInTheDocument();
76+
});
77+
78+
it("doesn't contain the inactive group after two toggles", () => {
79+
fireEvent.click(screen.getByTestId("show_hidden_groups"));
80+
fireEvent.click(screen.getByTestId("show_hidden_groups"));
81+
expect(screen.queryByText("group_0015")).not.toBeInTheDocument();
82+
});
83+
});

app/assets/javascripts/Components/graders_manager.jsx

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ class GradersManager extends React.Component {
2323
sections: {},
2424
isGraderDistributionModalOpen: false,
2525
show_hidden: false,
26+
show_hidden_groups: false,
2627
hidden_graders_count: 0,
28+
inactive_groups_count: 0,
2729
};
2830
}
2931

@@ -66,6 +68,15 @@ class GradersManager extends React.Component {
6668
if (this.groupsTable) this.groupsTable.resetSelection();
6769
if (this.criteriaTable) this.criteriaTable.resetSelection();
6870

71+
let inactive_groups_count = 0;
72+
res.groups.forEach(group => {
73+
if (group.members.length && group.members.every(member => member[2])) {
74+
group.inactive = true;
75+
inactive_groups_count += 1;
76+
} else {
77+
group.inactive = false;
78+
}
79+
});
6980
this.setState({
7081
graders: res.graders,
7182
groups: res.groups,
@@ -77,6 +88,7 @@ class GradersManager extends React.Component {
7788
hide_unassigned_criteria: res.hide_unassigned_criteria,
7889
isGraderDistributionModalOpen: false,
7990
hidden_graders_count: res.graders.filter(grader => grader.hidden).length,
91+
inactive_groups_count: inactive_groups_count,
8092
});
8193
});
8294
};
@@ -282,6 +294,11 @@ class GradersManager extends React.Component {
282294
this.setState({show_hidden});
283295
};
284296

297+
toggleShowHiddenGroups = event => {
298+
let show_hidden_groups = event.target.checked;
299+
this.setState({show_hidden_groups});
300+
};
301+
285302
render() {
286303
return (
287304
<div>
@@ -290,8 +307,11 @@ class GradersManager extends React.Component {
290307
openGraderDistributionModal={this.openGraderDistributionModal}
291308
unassignAll={this.unassignAll}
292309
showHidden={this.state.show_hidden}
310+
showHiddenGroups={this.state.show_hidden_groups}
293311
updateShowHidden={this.toggleShowHidden}
312+
updateShowHiddenGroups={this.toggleShowHiddenGroups}
294313
hiddenGradersCount={this.state.loading ? null : this.state.hidden_graders_count}
314+
hiddenGroupsCount={this.state.loading ? null : this.state.inactive_groups_count}
295315
/>
296316
<div className="mapping-tables">
297317
<div className="mapping-table">
@@ -346,6 +366,7 @@ class GradersManager extends React.Component {
346366
sections={this.state.sections}
347367
numCriteria={this.state.criteria.length}
348368
showCoverage={this.state.assign_graders_to_criteria}
369+
showInactive={this.state.show_hidden_groups}
349370
/>
350371
</TabPanel>
351372
<TabPanel>
@@ -509,6 +530,14 @@ class RawGroupsTable extends React.Component {
509530

510531
getColumns = () => {
511532
return [
533+
{
534+
accessor: "inactive",
535+
id: "inactive",
536+
width: 0,
537+
className: "rt-hidden",
538+
headerClassName: "rt-hidden",
539+
resizable: false,
540+
},
512541
{
513542
show: false,
514543
accessor: "_id",
@@ -582,6 +611,19 @@ class RawGroupsTable extends React.Component {
582611
];
583612
};
584613

614+
static getDerivedStateFromProps(props, state) {
615+
let filtered = state.filtered.filter(group => group.id !== "inactive");
616+
617+
if (!props.showInactive) {
618+
filtered.push({id: "inactive", value: false});
619+
}
620+
return {filtered};
621+
}
622+
623+
onFilteredChange = filtered => {
624+
this.setState({filtered});
625+
};
626+
585627
render() {
586628
return (
587629
<CheckboxTable
@@ -595,6 +637,8 @@ class RawGroupsTable extends React.Component {
595637
]}
596638
loading={this.props.loading}
597639
filterable
640+
filtered={this.state.filtered}
641+
onFilteredChange={this.onFilteredChange}
598642
{...this.props.getCheckboxProps()}
599643
/>
600644
);
@@ -682,11 +726,15 @@ const CriteriaTable = withSelection(RawCriteriaTable);
682726

683727
class GradersActionBox extends React.Component {
684728
render = () => {
685-
let showHiddenTooltip = "";
686-
if (this.props.hiddenGradersCount !== null) {
687-
showHiddenTooltip = I18n.t("graders.inactive_graders_count", {
688-
count: this.props.hiddenGradersCount || 0,
689-
});
729+
let showHiddenGraderTooltip = "";
730+
let showHiddenGroupsTooltip = "";
731+
if (this.props.hiddenGradersCount !== null && this.props.hiddenGroupsCount !== null) {
732+
showHiddenGraderTooltip = `${I18n.t("graders.inactive_graders_count", {
733+
count: this.props.hiddenGradersCount,
734+
})}`;
735+
showHiddenGroupsTooltip = `${I18n.t("activerecord.attributes.grouping.inactive_groups", {
736+
count: this.props.hiddenGroupsCount,
737+
})}`;
690738
}
691739

692740
return (
@@ -700,10 +748,28 @@ class GradersActionBox extends React.Component {
700748
onChange={this.props.updateShowHidden}
701749
className={"hide-user-checkbox"}
702750
/>
703-
<label title={showHiddenTooltip} htmlFor="show_hidden">
751+
<label title={showHiddenGraderTooltip} htmlFor="show_hidden">
704752
{I18n.t("tas.display_inactive")}
705753
</label>
706754
</span>
755+
<span>
756+
<input
757+
id="show_hidden_groups"
758+
name="show_hidden_groups"
759+
type="checkbox"
760+
checked={this.props.showHiddenGroups}
761+
onChange={this.props.updateShowHiddenGroups}
762+
className={"hide-user-checkbox"}
763+
data-testid={"show_hidden_groups"}
764+
/>
765+
<label
766+
title={showHiddenGroupsTooltip}
767+
htmlFor="show_hidden_groups"
768+
data-testid={"show_hidden_groups_tooltip"}
769+
>
770+
{I18n.t("groups.display_inactive")}
771+
</label>
772+
</span>
707773
<button onClick={this.props.assignAll}>
708774
<FontAwesomeIcon icon="fa-solid fa-user-plus" />
709775
{I18n.t("graders.actions.assign_grader")}
@@ -724,3 +790,4 @@ class GradersActionBox extends React.Component {
724790
export function makeGradersManager(elem, props) {
725791
render(<GradersManager {...props} />, elem);
726792
}
793+
export {GradersManager};

app/models/assignment.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ def current_grader_data
10821082
}
10831083
end
10841084

1085-
{
1085+
result = {
10861086
groups: groups,
10871087
criteria: criteria,
10881088
graders: graders,
@@ -1091,6 +1091,16 @@ def current_grader_data
10911091
hide_unassigned_criteria: self.hide_unassigned_criteria,
10921092
sections: assignment.course.sections.pluck(:id, :name).to_h
10931093
}
1094+
1095+
members_data = assignment.groupings.joins(student_memberships: { role: :user })
1096+
.pluck('groupings.id', 'users.user_name', 'memberships.membership_status', 'roles.hidden')
1097+
1098+
grouped_data = members_data.group_by { |x| x[0] }
1099+
grouped_data.each_value { |a| a.each { |b| b.delete_at(0) } }
1100+
1101+
result[:groups].each { |group| group[:members] = grouped_data[group[:_id]] }
1102+
1103+
result
10941104
end
10951105

10961106
# Retrieve data for submissions table.

config/locales/views/groups/en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ en:
2121
destroy:
2222
errors:
2323
do_not_have_a_group: You do not currently have a group.
24+
display_inactive: Display inactive groups
2425
due_date_extension: Due Date Extension
2526
duration_extension: Duration Extension
2627
empty: Empty Group

spec/models/assignment_spec.rb

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2573,6 +2573,7 @@ def grouping_count(groupings)
25732573
context 'groupings that have graders' do
25742574
let(:section) { create :section }
25752575
let(:student) { create :student, section: section }
2576+
let(:student2) { create :student, section: section }
25762577
let(:grouping) { create :grouping_with_inviter, inviter: student, assignment: assignment }
25772578
let(:ta) { create :ta }
25782579
it 'returns correct graders' do
@@ -2588,6 +2589,22 @@ def grouping_count(groupings)
25882589
}
25892590
expect(received_grader_info).to eq(expected_grader_info)
25902591
end
2592+
it 'returns correct member data' do
2593+
grouping.add_member(student2) # adding a second member to the grouping
2594+
Grouping.assign_all_tas([grouping], [ta.id], assignment)
2595+
2596+
received_data = assignment.current_grader_data
2597+
2598+
expect(received_data[:groups].size).to eq(1) # there should only be one group
2599+
2600+
# What the :members key of each group object in :groups should be (in the 'received_data' object)
2601+
expected_members_data = [student, student2].map do |s|
2602+
[s.user.user_name, s.memberships[0].membership_status, s.hidden]
2603+
end
2604+
actual_members_data = received_data[:groups][0][:members]
2605+
2606+
expect(actual_members_data).to eq(expected_members_data)
2607+
end
25912608
context 'graders are hidden' do
25922609
it 'returns correct hidden grader info' do
25932610
ta.update!(hidden: true)
@@ -2601,6 +2618,62 @@ def grouping_count(groupings)
26012618
end
26022619
end
26032620
end
2621+
2622+
# Ensures that the object returned by the Assignment.current_grader_data method has the desired structure
2623+
# which is expected (contractually) by front end code (that requests this data).
2624+
context 'structure of output data' do
2625+
it 'follows required structure' do
2626+
filled_assignment = create(:assignment_with_peer_review_and_groupings_results)
2627+
2628+
result = filled_assignment.current_grader_data
2629+
2630+
expect(result).to include(
2631+
groups: be_an(Array),
2632+
criteria: be_an(Array),
2633+
graders: be_an(Array),
2634+
assign_graders_to_criteria: be_in([true, false]),
2635+
anonymize_groups: be_in([true, false]),
2636+
hide_unassigned_criteria: be_in([true, false]),
2637+
sections: be_a(Hash)
2638+
)
2639+
2640+
result[:groups].each do |group|
2641+
expect(group).to include(members: be_an(Array))
2642+
2643+
group[:members].each do |member|
2644+
expect(member.length).to eq(3)
2645+
end
2646+
end
2647+
end
2648+
end
2649+
2650+
# Ensures that the object returned by the Assignment.current_grader_data method has the desired structure
2651+
# which is expected (contractually) by front end code (that requests this data).
2652+
context 'structure of output data' do
2653+
it 'follows required structure' do
2654+
filled_assignment = create(:assignment_with_peer_review_and_groupings_results)
2655+
2656+
result = filled_assignment.current_grader_data
2657+
2658+
expect(result).to include(
2659+
groups: be_an(Array),
2660+
criteria: be_an(Array),
2661+
graders: be_an(Array),
2662+
assign_graders_to_criteria: be_in([true, false]),
2663+
anonymize_groups: be_in([true, false]),
2664+
hide_unassigned_criteria: be_in([true, false]),
2665+
sections: be_a(Hash)
2666+
)
2667+
2668+
result[:groups].each do |group|
2669+
expect(group).to include(members: be_an(Array))
2670+
2671+
group[:members].each do |member|
2672+
expect(member.length).to eq(3)
2673+
end
2674+
end
2675+
end
2676+
end
26042677
end
26052678

26062679
describe '#current_results' do

0 commit comments

Comments
 (0)