Skip to content

Commit 2d5ebeb

Browse files
committed
feat(gradebook): import external assessment grades from CSV
Introduce the full external-assessment CSV import feature as a single slice on top of grade validation (pr3). Backend: - Course::Gradebook::ExternalAssessmentImportService — header-order tolerance, duplicate-header/identifier guards, reassigned-identifier detection, out-of-range flagging, and batched (bulk insert + upsert) grade writes. - external_assessment_imports controller (preview + create/commit), create/preview jbuilders, and import routes. Frontend: - ImportExternalAssessmentsWizard (upload → define → verify), with the ExternalGradeConflict prompt/table change-matrix and buildTemplate. - previewImport/commitImport operations + import API endpoints + types. - ManageExternalAssessmentsPanel gains the Import CSV entry point. Consolidates the previously planned pr4a (correctness) and pr4b (ux) into one PR: the import wizard and service are edited by both themes at the hunk level, so they do not separate into independently-reviewable slices. Fixes a pre-existing broken assertion in the import controller spec (course_externalassessments -> course_external_assessments) that prevented the replace-on-conflict test from running.
1 parent 38c0e8e commit 2d5ebeb

23 files changed

Lines changed: 5503 additions & 416 deletions
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
class Course::ExternalAssessmentImportsController < Course::ComponentController
3+
Service = Course::Gradebook::ExternalAssessmentImportService
4+
5+
def preview
6+
authorize! :manage_gradebook_weights, current_course
7+
@result = build_service.preview
8+
render 'preview'
9+
rescue Service::ImportError => e
10+
render json: { errors: e.payload }, status: :unprocessable_entity
11+
end
12+
13+
def create
14+
authorize! :manage_gradebook_weights, current_course
15+
@summary = build_service.commit(on_conflict: import_params[:onConflict])
16+
render 'create'
17+
rescue Service::ImportError => e
18+
render json: { errors: e.payload }, status: :unprocessable_entity
19+
end
20+
21+
private
22+
23+
def component
24+
current_component_host[:course_gradebook_component]
25+
end
26+
27+
def build_service
28+
permitted_params = import_params
29+
weighted_view_enabled = gradebook_settings.weighted_view_enabled
30+
Service.new(
31+
course: current_course,
32+
actor: current_user,
33+
components: permitted_params[:components].map do |c|
34+
{ name: c[:name],
35+
weightage: weighted_view_enabled ? c[:weightage].to_i : 0,
36+
maximum_grade: c[:maximumGrade].to_f }
37+
end,
38+
identifier_mode: permitted_params[:identifierMode],
39+
csv_data: permitted_params[:csvData]
40+
)
41+
end
42+
43+
def gradebook_settings
44+
@gradebook_settings ||= Course::Settings::GradebookComponent.new(component)
45+
end
46+
47+
def import_params
48+
@import_params ||= params.slice(:identifierMode, :csvData, :onConflict, :components).permit(
49+
:identifierMode, :csvData, :onConflict,
50+
components: [:name, :weightage, :maximumGrade]
51+
)
52+
end
53+
end

0 commit comments

Comments
 (0)