Skip to content

Commit 7fffdf8

Browse files
Outcome imports can customize criterion ratings
closes OUT-396 test plan - enable Academic Benchmark importing - initiate an import without overriding any criterion ratings - after the import is complete, confirm that the default calculation method, calculation int, points possible, mastery points and ratings are configured for the imported outcomes - on the account's outcome page, import any one of the newly imported standards - on the course's outcome page, import the newly imported account outcome - create an assignment and align to the course outcome - as a student, submit to the assignment - as a teacher, grade the assignment's rubric - initiate another import, specifying a calculation method, calculation int, points possible, mastery points and ratings - after import is complete, confirm that the learning outcomes have those values set, except for the one outcome that was aligned, which should have the default values Change-Id: If3a730b85548a0851b82c7da67dc4a2e8714eb74 Reviewed-on: https://gerrit.instructure.com/93948 Tested-by: Jenkins Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> QA-Review: Alex Ortiz-Rosado <aortiz@instructure.com> Product-Review: McCall Smith <mcsmith@instructure.com>
1 parent 96a4470 commit 7fffdf8

9 files changed

Lines changed: 290 additions & 29 deletions

File tree

app/controllers/outcomes_import_api_controller.rb

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ def available
3131

3232
def create
3333
return render json: { error: "must specify a guid to import" } unless params[:guid]
34-
return unless valid_guid(params[:guid])
35-
3634
begin
35+
err_msg = "Invalid parameters"
36+
options = valid_options(params)
37+
valid_guid(params[:guid])
38+
3739
err_msg = "Import failed to queue"
3840

3941
# AcademicBenchmark.queue_migration_for_guid can raise Canvas::Migration::Error
40-
migration = AcademicBenchmark.import(Array(params[:guid])).first
42+
migration = AcademicBenchmark.import(Array(params[:guid]), options).first
4143

4244
if migration
4345
render json: { migration_id: migration.id, guid: params[:guid] }
@@ -92,10 +94,54 @@ def has_api_config
9294
##
9395
def valid_guid(guid)
9496
unless guid =~ /[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}/i
95-
render json: { error: "GUID is invalid" }
96-
return false
97+
raise "GUID is invalid: #{guid}"
9798
end
98-
true
99+
end
100+
101+
def parse_int(value)
102+
Integer value
103+
rescue
104+
raise "invalid value, must be integer: #{value}"
105+
end
106+
107+
def valid_options(hash)
108+
options = {}
109+
if hash.key? :calculation_method
110+
options[:calculation_method] = parse_calculation_method hash[:calculation_method]
111+
options[:calculation_int] = parse_calculation_int options[:calculation_method], hash[:calculation_int]
112+
end
113+
options[:mastery_points] = parse_int hash[:mastery_points] if hash.key? :mastery_points
114+
options[:points_possible] = parse_int hash[:points_possible] if hash.key? :points_possible
115+
unless hash[:ratings].nil?
116+
raise "ratings expected to be an array" unless hash[:ratings].is_a?(Array)
117+
options[:ratings] = hash[:ratings].map {|r| parse_rating(r)}
118+
end
119+
options
120+
end
121+
122+
def parse_calculation_method(value)
123+
unless LearningOutcome.valid_calculation_method?(value)
124+
raise "invalid calculation_method: #{value}"
125+
end
126+
value
127+
end
128+
129+
def parse_calculation_int(calc_method, value)
130+
int = value.nil? ? nil : parse_int(value)
131+
unless LearningOutcome.valid_calculation_int?(int, calc_method)
132+
raise "invalid calculation_int: #{value}"
133+
end
134+
int
135+
end
136+
137+
def parse_rating(rating)
138+
if rating.nil? || !rating.is_a?(Hash)
139+
raise "invalid ratings value: #{rating}"
140+
end
141+
if rating[:description].nil? || !rating[:description].is_a?(String)
142+
raise "invalid description value: #{rating[:description]}"
143+
end
144+
{ :description => rating[:description], :points => parse_int(rating[:points]) }
99145
end
100146

101147
##

app/models/importers/learning_outcome_importer.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,19 @@ def self.import_from_migration(hash, migration, item=nil)
7171
item.workflow_state = 'active' if item.deleted?
7272
item.short_description = hash[:title]
7373
item.description = hash[:description]
74-
item.calculation_method ||= hash[:calculation_method]
75-
item.calculation_int ||= hash[:calculation_int]
74+
assessed = item.assessed?
75+
unless assessed
76+
item.calculation_method = hash[:calculation_method] || item.calculation_method
77+
item.calculation_int = hash[:calculation_int] || item.calculation_int
78+
end
7679

7780
if hash[:ratings]
7881
item.data = {:rubric_criterion=>{}}
79-
item.data[:rubric_criterion][:ratings] = hash[:ratings] ? hash[:ratings].map(&:symbolize_keys) : []
80-
item.data[:rubric_criterion][:mastery_points] = hash[:mastery_points]
81-
item.data[:rubric_criterion][:points_possible] = hash[:points_possible]
82+
unless assessed
83+
item.data[:rubric_criterion][:ratings] = hash[:ratings] ? hash[:ratings].map(&:symbolize_keys) : []
84+
item.data[:rubric_criterion][:mastery_points] = hash[:mastery_points]
85+
item.data[:rubric_criterion][:points_possible] = hash[:points_possible]
86+
end
8287
item.data[:rubric_criterion][:description] = item.short_description || item.description
8388
end
8489

app/models/learning_outcome.rb

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,32 @@ def infer_defaults
8787
end
8888

8989
def validate_calculation_int
90-
unless valid_calculation_int?(calculation_int, calculation_method)
91-
if valid_calculation_ints.to_a.empty?
90+
unless self.class.valid_calculation_int?(calculation_int, calculation_method)
91+
valid_ints = self.class.valid_calculation_ints(self.calculation_method)
92+
if valid_ints.to_a.empty?
9293
errors.add(:calculation_int, t(
9394
"A calculation value is not used with this calculation method"
9495
))
9596
else
9697
errors.add(:calculation_int, t(
9798
"'%{calculation_int}' is not a valid value for this calculation method. The value must be between '%{valid_calculation_ints_min}' and '%{valid_calculation_ints_max}'",
9899
:calculation_int => calculation_int,
99-
:valid_calculation_ints_min => valid_calculation_ints.min,
100-
:valid_calculation_ints_max => valid_calculation_ints.max
100+
:valid_calculation_ints_min => valid_ints.min,
101+
:valid_calculation_ints_max => valid_ints.max
101102
))
102103
end
103104
end
104105
end
105106

106-
def valid_calculation_method?(method=self.calculation_method)
107+
def self.valid_calculation_method?(method)
107108
CALCULATION_METHODS.keys.include?(method)
108109
end
109110

110-
def valid_calculation_ints(method=self.calculation_method)
111+
def self.valid_calculation_ints(method)
111112
VALID_CALCULATION_INTS[method]
112113
end
113114

114-
def valid_calculation_int?(int, method=self.calculation_method)
115+
def self.valid_calculation_int?(int, method)
115116
if valid_calculation_method?(method)
116117
valid_ints = valid_calculation_ints(method)
117118
(int.nil? && valid_ints.to_a.empty?) || valid_ints.include?(int)

gems/plugins/academic_benchmark/lib/academic_benchmark.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def self.config
1616

1717
class APIError < StandardError; end
1818

19-
def self.import(guid_or_guids)
19+
def self.import(guid_or_guids, options={})
2020
if !AcademicBenchmark.config[:api_key] || AcademicBenchmark.config[:api_key].empty?
2121
raise Canvas::Migration::Error.new("Not importing academic benchmark data because no API key is set")
2222
end
@@ -32,11 +32,11 @@ def self.import(guid_or_guids)
3232
end
3333

3434
Array(guid_or_guids).map do |guid|
35-
AcademicBenchmark.queue_migration_for_guid(guid, permissionful_user).first
35+
AcademicBenchmark.queue_migration_for_guid(guid, permissionful_user, options).first
3636
end
3737
end
3838

39-
def self.queue_migration_for_guid(guid, user)
39+
def self.queue_migration_for_guid(guid, user, options={})
4040
unless Account.site_admin.grants_right?(user, :manage_global_outcomes)
4141
raise Canvas::Migration::Error.new(I18n.t('academic_benchmark.no_permissions', "User isn't allowed to edit global outcomes"))
4242
end
@@ -49,6 +49,7 @@ def self.queue_migration_for_guid(guid, user)
4949
cm.migration_settings[:no_archive_file] = true
5050
cm.migration_settings[:skip_import_notification] = true
5151
cm.migration_settings[:skip_job_progress] = true
52+
cm.migration_settings[:migration_options] = options
5253
cm.strand = "academic_benchmark"
5354
cm.user = user
5455
cm.save!

gems/plugins/academic_benchmark/lib/academic_benchmark/converter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class Converter < Canvas::Migration::Migrator
55

66
def initialize(settings={})
77
super(settings, "academic_benchmark")
8+
@ratings_overrides = settings[:migration_options] || {}
89

910
ab_settings = AcademicBenchmark.config
1011

@@ -126,7 +127,7 @@ def process_leaves!(data)
126127

127128
def process_json_data(data)
128129
if data = find_by_prop(data, "type", "authority")
129-
outcomes = Standard.new(data).build_outcomes
130+
outcomes = Standard.new(data).build_outcomes(@ratings_overrides)
130131
@course[:learning_outcomes] << outcomes
131132
else
132133
err_msg = I18n.t("Couldn't find an authority to update")

gems/plugins/academic_benchmark/lib/academic_benchmark/standard.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,19 @@ def initialize(data, parent=nil)
1818
end
1919
end
2020

21-
def build_outcomes
21+
def build_outcomes(ratings={})
2222
hash = {:migration_id => guid, :vendor_guid => guid, :low_grade => low_grade, :high_grade => high_grade, :is_global_standard => true}
2323
hash[:description] = description
2424
if is_leaf?
2525
# create outcome
2626
hash[:type] = 'learning_outcome'
2727
hash[:title] = build_num_title
28-
set_default_ratings(hash)
28+
set_default_ratings(hash, ratings)
2929
else
3030
#create outcome group
3131
hash[:type] = 'learning_outcome_group'
3232
hash[:title] = build_title
33-
hash[:outcomes] = []
34-
@children.each do |chld|
35-
hash[:outcomes] << chld.build_outcomes
36-
end
33+
hash[:outcomes] = @children.map {|chld| chld.build_outcomes(ratings)}
3734
end
3835

3936
hash
@@ -139,12 +136,13 @@ def is_leaf?
139136
num && @children.empty?
140137
end
141138

142-
def set_default_ratings(hash)
139+
def set_default_ratings(hash, overrides={})
143140
hash[:ratings] = [{:description => "Exceeds Expectations", :points => 5},
144141
{:description => "Meets Expectations", :points => 3},
145142
{:description => "Does Not Meet Expectations", :points => 0}]
146143
hash[:mastery_points] = 3
147144
hash[:points_possible] = 5
145+
hash.merge!(overrides)
148146
end
149147
end
150148
end

gems/plugins/academic_benchmark/spec_canvas/academic_benchmark_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
@cm.migration_settings[:migration_type] = 'academic_benchmark_importer'
1414
@cm.migration_settings[:import_immediately] = true
1515
@cm.migration_settings[:base_url] = "http://example.com/"
16+
@cm.migration_settings[:migration_options] = {points_possible: 10, mastery_points: 6,
17+
ratings: [{description: 'Bad', points: 0}, {description: 'Awesome', points: 10}]}
1618
@cm.user = @user
1719
@cm.save!
1820

@@ -34,6 +36,13 @@
3436
@cm.save!
3537
end
3638

39+
def verify_rubric_criterion(outcome)
40+
expect(outcome.data[:rubric_criterion][:mastery_points]).to eq 6
41+
expect(outcome.data[:rubric_criterion][:points_possible]).to eq 10
42+
expect(outcome.data[:rubric_criterion][:ratings]).to eq [{description: 'Bad', points: 0},
43+
{description: 'Awesome', points: 10}]
44+
end
45+
3746
def verify_full_import
3847
@root_group = LearningOutcomeGroup.global_root_outcome_group
3948
expect(@root_group.child_outcome_groups.count).to eq 1
@@ -66,12 +75,15 @@ def verify_full_import
6675
expect(f.child_outcome_links.count).to eq 3
6776

6877
g = LearningOutcome.global.where(migration_id: "ggggggggggggggggg").first
78+
verify_rubric_criterion(g)
6979
expect(g.short_description).to eq "K.CC.1"
7080
expect(g.description).to eq "Count to 100 by ones and by tens."
7181
g = LearningOutcome.global.where(migration_id: "hhhhhhhhhhhhhhhh").first
82+
verify_rubric_criterion(g)
7283
expect(g.short_description).to eq "K.CC.2"
7384
expect(g.description).to eq "Count forward beginning from a given number within the known sequence (instead of having to begin at 1)."
7485
g = LearningOutcome.global.where(migration_id: "iiiiiiiiiiiiiiiii").first
86+
verify_rubric_criterion(g)
7587
expect(g.short_description).to eq "K.CC.3"
7688
expect(g.description).to eq "Write numbers from 0 to 20. Represent a number of objects with a written numeral 0-20 (with 0 representing a count of no objects)."
7789

@@ -95,6 +107,7 @@ def verify_full_import
95107
expect(l.child_outcome_links.count).to eq 1
96108

97109
m = LearningOutcome.global.where(migration_id: "mmmmmmmmmmm").first
110+
verify_rubric_criterion(g)
98111
expect(m.short_description).to eq "1.DD.1"
99112
expect(m.description).to eq "And something else"
100113
expect(m.title).to eq "1.DD.1"

0 commit comments

Comments
 (0)