From ed2910b0eaa1cbca5f29fb5d85fb75369536c304 Mon Sep 17 00:00:00 2001 From: Yanzhen Chen Date: Thu, 18 Jun 2026 18:29:29 -0400 Subject: [PATCH 1/2] Add grades tab upload flow --- app/controllers/assignments_controller.rb | 13 ++ .../Modals/assignment_grades_upload_modal.jsx | 69 ++++++++++ .../Components/assignment_summary.jsx | 2 + .../Components/assignment_summary_table.jsx | 17 +++ app/models/assignment.rb | 120 +++++++++++++++++ app/policies/assignment_policy.rb | 1 + app/views/assignments/summary.html.erb | 3 +- config/locales/views/assignments/en.yml | 3 + config/routes.rb | 1 + .../assignments_controller_spec.rb | 47 +++++++ spec/models/assignment_spec.rb | 127 ++++++++++++++++++ 11 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 app/javascript/Components/Modals/assignment_grades_upload_modal.jsx diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 9029566850..4297311054 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -268,6 +268,19 @@ def summary end end + def upload_grades + @assignment = record + begin + data = process_file_upload(['.csv']) + rescue StandardError => e + flash_message(:error, e.message) + else + result = @assignment.import_marks_from_csv(data[:contents], params[:overwrite], current_role) + flash_csv_result(result) + end + redirect_to action: 'summary', id: @assignment.id + end + def download_test_results @assignment = record respond_to do |format| diff --git a/app/javascript/Components/Modals/assignment_grades_upload_modal.jsx b/app/javascript/Components/Modals/assignment_grades_upload_modal.jsx new file mode 100644 index 0000000000..1fbfa01d1c --- /dev/null +++ b/app/javascript/Components/Modals/assignment_grades_upload_modal.jsx @@ -0,0 +1,69 @@ +import React from "react"; +import Modal from "react-modal"; + +class AssignmentGradesUploadModal extends React.Component { + componentDidMount() { + Modal.setAppElement("body"); + } + + authenticityToken() { + return document.querySelector("meta[name='csrf-token']")?.content || ""; + } + + render() { + return ( + +

+ {I18n.t("upload_the", { + item: I18n.t("assignments.grades"), + })} +

+
+
+ +

+ +

+

+ + +

+

+ +

+
+ + +
+
+
+
+ ); + } +} + +export default AssignmentGradesUploadModal; diff --git a/app/javascript/Components/assignment_summary.jsx b/app/javascript/Components/assignment_summary.jsx index 827299c204..9a5b6abc1e 100644 --- a/app/javascript/Components/assignment_summary.jsx +++ b/app/javascript/Components/assignment_summary.jsx @@ -18,6 +18,7 @@ class AssignmentSummary extends React.Component { course_id={this.props.course_id} assignment_id={this.props.assessment_id} is_instructor={this.props.is_instructor} + encodings={this.props.encodings} lti_deployments={this.props.lti_deployments} /> @@ -38,6 +39,7 @@ class AssignmentSummary extends React.Component { course_id={this.props.course_id} assignment_id={this.props.assessment_id} is_instructor={this.props.is_instructor} + encodings={this.props.encodings} lti_deployments={this.props.lti_deployments} /> ); diff --git a/app/javascript/Components/assignment_summary_table.jsx b/app/javascript/Components/assignment_summary_table.jsx index 28c5f4de21..fa165492e4 100644 --- a/app/javascript/Components/assignment_summary_table.jsx +++ b/app/javascript/Components/assignment_summary_table.jsx @@ -1,6 +1,7 @@ import React from "react"; import {getMarkingStates, selectFilter} from "./Helpers/table_helpers"; +import AssignmentGradesUploadModal from "./Modals/assignment_grades_upload_modal"; import DownloadTestResultsModal from "./Modals/download_test_results_modal"; import LtiGradeModal from "./Modals/send_lti_grades_modal"; import {createColumnHelper} from "@tanstack/react-table"; @@ -20,6 +21,7 @@ export class AssignmentSummaryTable extends React.Component { marking_states: markingStates, markingStateFilter: "all", showDownloadTestsModal: false, + showGradesUploadModal: false, showLtiGradeModal: false, lti_deployments: [], columnFilters: [{id: "inactive", value: false}], @@ -361,6 +363,10 @@ export class AssignmentSummaryTable extends React.Component { this.setState({showDownloadTestsModal: true}); }; + onGradesUploadModal = () => { + this.setState({showGradesUploadModal: true}); + }; + onLtiGradeModal = () => { this.setState({showLtiGradeModal: true}); }; @@ -433,6 +439,10 @@ export class AssignmentSummaryTable extends React.Component { {I18n.t("download")} +