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

Allow inactive groups in the graders table to be toggled for display #6778

Merged
merged 29 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fa0d6bc
Allow inactive grader groups to be displayed
Bruce-8 Oct 20, 2023
6359ef0
Updated Changelog and moved checkbox to righthand pane
Bruce-8 Oct 20, 2023
08e6e93
flash-message-for-inactive-grader
mimischly7 Oct 20, 2023
5f8f4ed
updating-Changelog
mimischly7 Oct 20, 2023
a1db588
enriching-data-returned-by-graders-controller-index-action
mimischly7 Oct 25, 2023
1bb7085
Changed front-end logic with filtering groups
Bruce-8 Oct 27, 2023
4d02bf7
Changed location of group string
Bruce-8 Oct 27, 2023
59c1898
Changed location of group string
Bruce-8 Oct 27, 2023
3a70425
Modified Changelog
Bruce-8 Oct 27, 2023
05630bb
Revert changes for flash message
Bruce-8 Oct 28, 2023
82b578c
Merge branch 'master' of https:/MarkUsProject/Markus into…
Bruce-8 Oct 28, 2023
b6226ed
Re-added deleted space in graders_manager
Bruce-8 Oct 28, 2023
a1e9313
working-code-for-getting-members-efficiently
mimischly7 Nov 11, 2023
f6e81b7
finalizing-backend-improvements
mimischly7 Nov 17, 2023
0ae0368
rspec-test-for-Assignment-method
mimischly7 Nov 17, 2023
d7b4fbd
Merge branch 'master' of https:/MarkUsProject/Markus into…
Bruce-8 Nov 21, 2023
f23392a
Fixed errors and changelog
Bruce-8 Nov 21, 2023
5720893
Added front-end tests
Bruce-8 Nov 21, 2023
289f81d
Increased coverage of new lines of code
Bruce-8 Nov 22, 2023
09561aa
Final test fixes (front-end)
Bruce-8 Nov 24, 2023
ab7f5dc
rspec-test-for-assign-all-graders
mimischly7 Nov 24, 2023
196be58
Fixed Changelog and merge conflicts
Bruce-8 Dec 1, 2023
87681b2
Merge branch 'display-inactive-graders' of https:/Bruce-8…
Bruce-8 Dec 1, 2023
7723b9b
final-touches
mimischly7 Dec 5, 2023
ca70459
conflict-resolution
mimischly7 Dec 5, 2023
29719a9
Modified Changelog
Bruce-8 Dec 8, 2023
e2c9a24
removing-unnecessary-lines
mimischly7 Dec 10, 2023
61e5dee
Merge branch 'master' into display-inactive-graders
david-yz-liu Dec 14, 2023
d659e07
Pin CI chromedriver version
david-yz-liu Dec 14, 2023
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
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Ensure starter files are passed to autotester in sorted order (#6771)
- Resolved issue 6677 by taking timed assessment's duration into account when determining when grading can begin (#6845)
- Fix loading results page when group is created after the due date (#6863)
- Allow inactive groups in the graders table to be toggled for display (#6778)
Copy link
Contributor

Choose a reason for hiding this comment

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

There's been another release! Please do another pull from upstream master and put this entry under an [unreleased] section.

- Autocomplete with active students only for matching process in 'assign scans' (#6844)
- Fix member filtering in Groups table (#6842)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/***
* Tests for GradersManager Component
*/

import {GradersManager} from "../graders_manager";
import {render, screen, fireEvent} from "@testing-library/react";

jest.mock("@fortawesome/react-fontawesome", () => ({
FontAwesomeIcon: () => {
return null;
},
}));

describe("For the GradersManager's display of inactive groups", () => {
let groups_sample;
beforeEach(() => {
groups_sample = [
{
_id: 15,
members: [
["c9nielse", "inviter", true],
["c8szyman", "accepted", true],
],
inactive: false,
grace_credits: 5,
remaining_grace_credits: 4,
group_name: "group_0015",
graders: [],
criteria_coverage_count: 0,
},
{
_id: 15,
members: [
["d2lifese", "inviter", false],
["a3kjcbod", "accepted", false],
],
inactive: false,
grace_credits: 5,
remaining_grace_credits: 4,
group_name: "group_0014",
graders: [],
criteria_coverage_count: 0,
},
];
fetch.mockReset();
fetch.mockResolvedValueOnce({
ok: true,
json: jest.fn().mockResolvedValueOnce({
graders: [],
criteria: [],
assign_graders_to_criteria: false,
loading: false,
sections: {},
anonymize_groups: false,
hide_unassigned_criteria: false,
isGraderDistributionModalOpen: false,
groups: groups_sample,
}),
});
render(<GradersManager sections={{}} course_id={1} assignment_id={1} />);
});

it("contains the correct amount of inactive groups in the hidden tooltip", () => {
expect(screen.getByTestId("show_hidden_groups_tooltip").getAttribute("title")).toEqual(
"1 inactive group"
);
});

it("initially contains the active group", () => {
expect(screen.getByText("group_0014")).toBeInTheDocument();
});

it("contains the inactive group after a single toggle", () => {
fireEvent.click(screen.getByTestId("show_hidden_groups"));
expect(screen.getByText("group_0015")).toBeInTheDocument();
});

it("doesn't contain the inactive group after two toggles", () => {
fireEvent.click(screen.getByTestId("show_hidden_groups"));
fireEvent.click(screen.getByTestId("show_hidden_groups"));
expect(screen.queryByText("group_0015")).not.toBeInTheDocument();
});
});
79 changes: 73 additions & 6 deletions app/assets/javascripts/Components/graders_manager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ class GradersManager extends React.Component {
sections: {},
isGraderDistributionModalOpen: false,
show_hidden: false,
show_hidden_groups: false,
hidden_graders_count: 0,
inactive_groups_count: 0,
};
}

Expand Down Expand Up @@ -66,6 +68,15 @@ class GradersManager extends React.Component {
if (this.groupsTable) this.groupsTable.resetSelection();
if (this.criteriaTable) this.criteriaTable.resetSelection();

let inactive_groups_count = 0;
res.groups.forEach(group => {
if (group.members.length && group.members.every(member => member[2])) {
group.inactive = true;
inactive_groups_count += 1;
} else {
group.inactive = false;
}
});
this.setState({
graders: res.graders,
groups: res.groups,
Expand All @@ -77,6 +88,7 @@ class GradersManager extends React.Component {
hide_unassigned_criteria: res.hide_unassigned_criteria,
isGraderDistributionModalOpen: false,
hidden_graders_count: res.graders.filter(grader => grader.hidden).length,
inactive_groups_count: inactive_groups_count,
});
});
};
Expand Down Expand Up @@ -282,6 +294,11 @@ class GradersManager extends React.Component {
this.setState({show_hidden});
};

toggleShowHiddenGroups = event => {
let show_hidden_groups = event.target.checked;
this.setState({show_hidden_groups});
};

render() {
return (
<div>
Expand All @@ -290,8 +307,11 @@ class GradersManager extends React.Component {
openGraderDistributionModal={this.openGraderDistributionModal}
unassignAll={this.unassignAll}
showHidden={this.state.show_hidden}
showHiddenGroups={this.state.show_hidden_groups}
updateShowHidden={this.toggleShowHidden}
updateShowHiddenGroups={this.toggleShowHiddenGroups}
hiddenGradersCount={this.state.loading ? null : this.state.hidden_graders_count}
hiddenGroupsCount={this.state.loading ? null : this.state.inactive_groups_count}
/>
<div className="mapping-tables">
<div className="mapping-table">
Expand Down Expand Up @@ -346,6 +366,7 @@ class GradersManager extends React.Component {
sections={this.state.sections}
numCriteria={this.state.criteria.length}
showCoverage={this.state.assign_graders_to_criteria}
showInactive={this.state.show_hidden_groups}
/>
</TabPanel>
<TabPanel>
Expand Down Expand Up @@ -509,6 +530,14 @@ class RawGroupsTable extends React.Component {

getColumns = () => {
return [
{
accessor: "inactive",
id: "inactive",
width: 0,
className: "rt-hidden",
headerClassName: "rt-hidden",
resizable: false,
},
{
show: false,
accessor: "_id",
Expand Down Expand Up @@ -582,6 +611,19 @@ class RawGroupsTable extends React.Component {
];
};

static getDerivedStateFromProps(props, state) {
let filtered = state.filtered.filter(group => group.id !== "inactive");

if (!props.showInactive) {
filtered.push({id: "inactive", value: false});
}
return {filtered};
}

onFilteredChange = filtered => {
this.setState({filtered});
};

render() {
return (
<CheckboxTable
Expand All @@ -595,6 +637,8 @@ class RawGroupsTable extends React.Component {
]}
loading={this.props.loading}
filterable
filtered={this.state.filtered}
onFilteredChange={this.onFilteredChange}
{...this.props.getCheckboxProps()}
/>
);
Expand Down Expand Up @@ -682,11 +726,15 @@ const CriteriaTable = withSelection(RawCriteriaTable);

class GradersActionBox extends React.Component {
render = () => {
let showHiddenTooltip = "";
if (this.props.hiddenGradersCount !== null) {
showHiddenTooltip = I18n.t("graders.inactive_graders_count", {
count: this.props.hiddenGradersCount || 0,
});
let showHiddenGraderTooltip = "";
let showHiddenGroupsTooltip = "";
if (this.props.hiddenGradersCount !== null && this.props.hiddenGroupsCount !== null) {
showHiddenGraderTooltip = `${I18n.t("graders.inactive_graders_count", {
count: this.props.hiddenGradersCount,
})}`;
showHiddenGroupsTooltip = `${I18n.t("activerecord.attributes.grouping.inactive_groups", {
count: this.props.hiddenGroupsCount,
})}`;
}

return (
Expand All @@ -700,10 +748,28 @@ class GradersActionBox extends React.Component {
onChange={this.props.updateShowHidden}
className={"hide-user-checkbox"}
/>
<label title={showHiddenTooltip} htmlFor="show_hidden">
<label title={showHiddenGraderTooltip} htmlFor="show_hidden">
{I18n.t("tas.display_inactive")}
</label>
</span>
<span>
<input
id="show_hidden_groups"
name="show_hidden_groups"
type="checkbox"
checked={this.props.showHiddenGroups}
onChange={this.props.updateShowHiddenGroups}
className={"hide-user-checkbox"}
data-testid={"show_hidden_groups"}
/>
<label
title={showHiddenGroupsTooltip}
htmlFor="show_hidden_groups"
data-testid={"show_hidden_groups_tooltip"}
>
{I18n.t("groups.display_inactive")}
</label>
</span>
<button onClick={this.props.assignAll}>
<FontAwesomeIcon icon="fa-solid fa-user-plus" />
{I18n.t("graders.actions.assign_grader")}
Expand All @@ -724,3 +790,4 @@ class GradersActionBox extends React.Component {
export function makeGradersManager(elem, props) {
render(<GradersManager {...props} />, elem);
}
export {GradersManager};
15 changes: 14 additions & 1 deletion app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ def current_grader_data
}
end

{
result = {
groups: groups,
criteria: criteria,
graders: graders,
Expand All @@ -1091,6 +1091,19 @@ def current_grader_data
hide_unassigned_criteria: self.hide_unassigned_criteria,
sections: assignment.course.sections.pluck(:id, :name).to_h
}

result.default_proc = proc { |h, k| h[k] = [] }
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't necessary


members_data = assignment.groupings.joins(student_memberships: { role: :user })
.pluck('groupings.id', 'users.user_name', 'memberships.membership_status', 'roles.hidden')

grouped_data = members_data.group_by { |x| x[0] }
grouped_data.each_value { |a| a.each { |b| b.delete_at(0) } }
grouped_data.default_proc = proc { |h, k| h[k] = [] }
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't necessary


result[:groups].each { |group| group[:members] = grouped_data[group[:_id]] }

result
end

# Retrieve data for submissions table.
Expand Down
1 change: 1 addition & 0 deletions config/locales/views/groups/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ en:
destroy:
errors:
do_not_have_a_group: You do not currently have a group.
display_inactive: Display inactive groups
due_date_extension: Due Date Extension
duration_extension: Duration Extension
empty: Empty Group
Expand Down
73 changes: 73 additions & 0 deletions spec/models/assignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2573,6 +2573,7 @@ def grouping_count(groupings)
context 'groupings that have graders' do
let(:section) { create :section }
let(:student) { create :student, section: section }
let(:student2) { create :student, section: section }
let(:grouping) { create :grouping_with_inviter, inviter: student, assignment: assignment }
let(:ta) { create :ta }
it 'returns correct graders' do
Expand All @@ -2588,6 +2589,22 @@ def grouping_count(groupings)
}
expect(received_grader_info).to eq(expected_grader_info)
end
it 'returns correct member data' do
grouping.add_member(student2) # adding a second member to the grouping
Grouping.assign_all_tas([grouping], [ta.id], assignment)

received_data = assignment.current_grader_data

expect(received_data[:groups].size).to eq(1) # there should only be one group

# What the :members key of each group object in :groups should be (in the 'received_data' object)
expected_members_data = [student, student2].map do |s|
[s.user.user_name, s.memberships[0].membership_status, s.hidden]
end
actual_members_data = received_data[:groups][0][:members]

expect(actual_members_data).to eq(expected_members_data)
end
context 'graders are hidden' do
it 'returns correct hidden grader info' do
ta.update!(hidden: true)
Expand All @@ -2601,6 +2618,62 @@ def grouping_count(groupings)
end
end
end

# Ensures that the object returned by the Assignment.current_grader_data method has the desired structure
# which is expected (contractually) by front end code (that requests this data).
context 'structure of output data' do
it 'follows required structure' do
filled_assignment = create(:assignment_with_peer_review_and_groupings_results)

result = filled_assignment.current_grader_data

expect(result).to include(
groups: be_an(Array),
criteria: be_an(Array),
graders: be_an(Array),
assign_graders_to_criteria: be_in([true, false]),
anonymize_groups: be_in([true, false]),
hide_unassigned_criteria: be_in([true, false]),
sections: be_a(Hash)
)

result[:groups].each do |group|
expect(group).to include(members: be_an(Array))

group[:members].each do |member|
expect(member.length).to eq(3)
end
end
end
end

# Ensures that the object returned by the Assignment.current_grader_data method has the desired structure
# which is expected (contractually) by front end code (that requests this data).
context 'structure of output data' do
it 'follows required structure' do
filled_assignment = create(:assignment_with_peer_review_and_groupings_results)

result = filled_assignment.current_grader_data

expect(result).to include(
groups: be_an(Array),
criteria: be_an(Array),
graders: be_an(Array),
assign_graders_to_criteria: be_in([true, false]),
anonymize_groups: be_in([true, false]),
hide_unassigned_criteria: be_in([true, false]),
sections: be_a(Hash)
)

result[:groups].each do |group|
expect(group).to include(members: be_an(Array))

group[:members].each do |member|
expect(member.length).to eq(3)
end
end
end
end
end

describe '#current_results' do
Expand Down