Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 additions & 0 deletions app/controllers/course/external_assessment_imports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true
class Course::ExternalAssessmentImportsController < Course::ComponentController
Service = Course::Gradebook::ExternalAssessmentImportService

def preview
authorize! :manage_gradebook_weights, current_course
@result = build_service.preview
render 'preview'
rescue Service::ImportError => e
render json: { errors: e.payload }, status: :unprocessable_entity
end

def create
authorize! :manage_gradebook_weights, current_course
@summary = build_service.commit(on_conflict: import_params[:onConflict])
render 'create'
rescue Service::ImportError => e
render json: { errors: e.payload }, status: :unprocessable_entity
end

private

def component
current_component_host[:course_gradebook_component]
end

def build_service
permitted_params = import_params
Service.new(
course: current_course,
actor: current_user,
identifier_mode: permitted_params[:identifierMode],
identifier_column: permitted_params[:identifierColumn],
csv_data: permitted_params[:csvData],
mappings: (permitted_params[:mappings] || []).map do |m|
{ header: m[:header], action: m[:action], target: m[:target],
max_grade: m[:maxGrade].presence&.to_f, weight: mapping_weight(m) }
end
)
end

# Weights only mean anything in the weighted view. Zero any incoming weight when
# the view is disabled so a stale request can't write a non-zero weight into a
# course that isn't weighting grades.
def mapping_weight(mapping)
return 0 unless @settings.weighted_view_enabled

mapping[:weight].presence&.to_f
end

def import_params
@import_params ||= params.permit(
:identifierMode, :identifierColumn, :csvData, :onConflict,
mappings: [:header, :action, :target, :maxGrade, :weight]
)
end
end
48 changes: 19 additions & 29 deletions app/controllers/course/gradebook_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,9 @@ def index

def update_weights
authorize! :manage_gradebook_weights, current_course
updates = (update_weights_params[:weights] || []).map { |entry| parse_weight_entry(entry) }
level_config = persist_weight_updates(updates)
response_body = { weights: serialize_weight_updates(updates) }
response_body[:levelContribution] = serialize_level_contribution(level_config) if level_config
render json: response_body
@updates = (update_weights_params[:weights] || []).map { |entry| parse_weight_entry(entry) }
@level_config = persist_weight_updates(@updates)
render 'update_weights'
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound, ArgumentError => e
render json: { errors: { base: e.message } }, status: :unprocessable_entity
end
Expand All @@ -45,10 +43,26 @@ def persist_weight_updates(updates)
Course::Gradebook::TabContribution.bulk_update(course: current_course, updates: tab_updates)
Course::Gradebook::ExternalContribution.bulk_update(course: current_course, updates: external_updates)
level_config = persist_level_contribution
persist_cap_total
end
level_config
end

# Persists the gradebook-wide cap-at-100 policy flag. Skipped when the client
# didn't send it, so unrelated weight saves leave the setting untouched.
def persist_cap_total
return unless params.key?(:capTotal)

# cast(nil) is nil, which would defeat the boolean == comparison below (false != nil)
# and persist a non-boolean; coerce to a strict boolean so an omitted-but-present
# value settles to false instead of forcing a needless write on every request.
new_value = ActiveRecord::Type::Boolean.new.cast(params[:capTotal]) || false
return if @settings.cap_weighted_total == new_value

@settings.cap_weighted_total = new_value
current_course.save!
end

def authorize_read_gradebook!
authorize! :read_gradebook, current_course
end
Expand Down Expand Up @@ -128,30 +142,6 @@ def level_contribution_attrs
attrs
end

def serialize_level_contribution(config)
{
enabled: config.enabled,
formula: config.formula,
weight: config.weight.to_f,
show: config.show,
clamp: config.clamp
}
end

def serialize_weight_updates(updates)
updates.map do |u|
entry = { tabId: u[:tab_id], weight: u[:weight], weightMode: u[:weight_mode].to_s,
keepHighest: u[:keep_highest],
excludedAssessmentIds: u[:excluded_assessment_ids] }
if u[:weight_mode].to_s == 'custom'
entry[:assessmentWeights] = u[:assessment_weights].map do |aw|
{ assessmentId: aw[:assessment_id], weight: aw[:weight] }
end
end
entry
end
end

def component
current_component_host[:course_gradebook_component]
end
Expand Down
15 changes: 15 additions & 0 deletions app/models/course/settings/gradebook_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,19 @@ def weighted_view_enabled
def weighted_view_enabled=(value)
settings.weighted_view_enabled = ActiveRecord::Type::Boolean.new.cast(value)
end

# Returns whether the weighted total is capped at 100% (disabled by default). A
# gradebook-wide grading policy, so it lives beside weighted_view_enabled.
#
# @return [Boolean] Setting on whether the weighted total is capped at 100%.
def cap_weighted_total
ActiveRecord::Type::Boolean.new.cast(settings.cap_weighted_total) || false
end

# Enable or disable capping the weighted total at 100%.
#
# @param [Boolean|Integer|String] value Setting on whether the weighted total is capped.
def cap_weighted_total=(value)
settings.cap_weighted_total = ActiveRecord::Type::Boolean.new.cast(value)
end
end
Loading