diff --git a/Changelog.md b/Changelog.md index 73b8de355d..96e0ea6b02 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ - Improve textviewer rendering speed (#7211) - Add periodic roster syncing via LTI (#7178) +- Allow instructors to assign graders by section (#7179) ### 🐛 Bug fixes - Fix incorrect calculation of token penalties when submissions are on time (#7216) diff --git a/app/assets/javascripts/Components/Modals/section_distribution_modal.js b/app/assets/javascripts/Components/Modals/section_distribution_modal.js new file mode 100644 index 0000000000..3ea46e9ec0 --- /dev/null +++ b/app/assets/javascripts/Components/Modals/section_distribution_modal.js @@ -0,0 +1,80 @@ +import React from "react"; +import Modal from "react-modal"; +import PropTypes from "prop-types"; + +export class SectionDistributionModal extends React.Component { + static defaultProps = { + override: false, + }; + + constructor(props) { + super(props); + this.input = React.createRef(); + this.sectionsArray = Object.values(this.props.sections).sort(); + this.graderMap = this.props.graders.reduce((map, grader) => { + map[grader.user_name] = grader._id; + return map; + }, {}); + } + + componentDidMount() { + Modal.setAppElement("body"); + } + + onSubmit = event => { + event.preventDefault(); + const form = new FormData(this.input.current); + const assignments = {}; + form.forEach((value, key) => { + assignments[key] = this.graderMap[value]; + }); + this.props.onSubmit(assignments); + }; + + renderSectionRow = section => { + const {graders} = this.props; + return ( +
+ + +
+ ); + }; + + render() { + return ( + +
+
+

{I18n.t("graders.assign_by_section_modal_title")}

+

{I18n.t("graders.assign_by_section_instruction")}

+ {this.sectionsArray.map(section => this.renderSectionRow(section))} +
+
+ +
+
+
+ ); + } +} + +SectionDistributionModal.propTypes = { + graders: PropTypes.arrayOf(PropTypes.object).isRequired, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + sections: PropTypes.objectOf(PropTypes.string).isRequired, +}; diff --git a/app/assets/javascripts/Components/graders_manager.jsx b/app/assets/javascripts/Components/graders_manager.jsx index cdc70c2f58..8ad927a2ac 100644 --- a/app/assets/javascripts/Components/graders_manager.jsx +++ b/app/assets/javascripts/Components/graders_manager.jsx @@ -6,6 +6,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import {withSelection, CheckboxTable} from "./markus_with_selection_hoc"; import {selectFilter} from "./Helpers/table_helpers"; import {GraderDistributionModal} from "./Modals/graders_distribution_modal"; +import {SectionDistributionModal} from "./Modals/section_distribution_modal"; class GradersManager extends React.Component { constructor(props) { @@ -22,6 +23,7 @@ class GradersManager extends React.Component { hide_unassigned_criteria: false, sections: {}, isGraderDistributionModalOpen: false, + isSectionDistributionModalOpen: false, show_hidden: false, show_hidden_groups: false, hidden_graders_count: 0, @@ -51,6 +53,11 @@ class GradersManager extends React.Component { isGraderDistributionModalOpen: true, }); }; + openSectionDistributionModal = () => { + this.setState({ + isSectionDistributionModalOpen: true, + }); + }; fetchData = () => { fetch(Routes.course_assignment_graders_path(this.props.course_id, this.props.assignment_id), { @@ -87,6 +94,7 @@ class GradersManager extends React.Component { anonymize_groups: res.anonymize_groups, hide_unassigned_criteria: res.hide_unassigned_criteria, isGraderDistributionModalOpen: false, + isSectionDistributionModalOpen: false, hidden_graders_count: res.graders.filter(grader => grader.hidden).length, inactive_groups_count: inactive_groups_count, }); @@ -124,6 +132,25 @@ class GradersManager extends React.Component { }).then(this.fetchData); }; + assignSections = assignments => { + let sections = Object.keys(assignments); + let graders = Object.values(assignments); + $.post({ + url: Routes.global_actions_course_assignment_graders_path( + this.props.course_id, + this.props.assignment_id + ), + data: { + global_actions: "assign_sections", + current_table: this.state.tableName, + skip_empty_submissions: this.state.skip_empty_submissions, + assignments: assignments, + sections: sections, + graders: graders, + }, + }).then(this.fetchData); + }; + assignRandomly = weightings => { let groups = this.groupsTable ? this.groupsTable.state.selection : []; let criteria = this.criteriaTable ? this.criteriaTable.state.selection : []; @@ -305,6 +332,7 @@ class GradersManager extends React.Component { )} + {this.state.isSectionDistributionModalOpen && ( + this.setState({isSectionDistributionModalOpen: false})} + onSubmit={this.assignSections} + graders={this.state.graders} + sections={this.state.sections} + /> + )} ); } @@ -778,6 +815,10 @@ class GradersActionBox extends React.Component { {I18n.t("graders.actions.randomly_assign_graders")} +