Allow all updates on assessed outcomes via api
closes OUT-1546 Opens the "Update an outcome" API endpoint to assessed outcomes. When total points possible are updated, then the LMGB values should scale according to the original points possible, while the student graph showing mastery scores should stay the same, since its displayed as a percentage of total score. test plan: - start up canvas - create a course level outcome using the default values for criterion ratings, mastery points, calculation method and calculation int - create an assignment - align the outcome to the assignment by creating a rubric - as a student, submit to the assignment - as a teacher, grade the assignment and provide a 5 out of 5 for the rubric - in the account settings page, enable `Learning Mastery Gradebook` and `Student Learning Mastery Gradebook` feature options - in the LMGB, confirm that the student has a score of "5 / 3" - click on the student's name and view the alignments - confirm that the alignment show a score of "5 / 3" - hover over the three dots next to "1 Alignment" to confirm the graph shows a single data point with 100% - confirm that you cannot edit the outcome in the ui - using the canvas api, change the outcome's total points possible from 5 to 10 by updating the points on the top rating: instructions for performing authenticated requests: https://canvas.instructure.com/doc/api/index.html instructions for updating an outcome via the api: https://canvas.instructure.com/doc/api/outcomes.html you can determine the id of the latest created outcome in the rails console: % docker-compose run --rm web bin/rails console > LearningOutcome.last.id here's an example update in JSON that updates the total points possible to 10: { "display_name": "", "title": "Outcome-2018-02-16.101", "description": "", "calculation_method": "decaying_average", "calculation_int": 65, "mastery_points": 3, "ratings": [ { "description": "Exceeds Expectations", "points": 10 }, { "description": "Meets Expectations", "points": 3 }, { "description": "Does Not Meet Expectations", "points": 0 } ] } - in the LMGB, confirm that the student has a score of "10 / 3" - click on the student's name and view the alignments - confirm that the alignment show a score of "10 / 3" - hover over the three dots next to "1 Alignment" to confirm the graph shows a single data point with 100% - using the canvas api, change the outcome's total points possible from 10 to 5 by updating the points on the top rating - create another assignment - align the outcome to the assignment by creating a rubric - as a student, submit to the assignment - as a teacher, grade the assignment and provide a 2.5 out of 5 for the rubric - in the LMGB, confirm that the student has a score of "3.38 / 3" (decaying average method: 2.5 * .65 + 5.0 * .35 = 3.375) - click on the student's name and view the alignments - confirm that the alignment show a score of "3.38 / 3" - hover over the three dots next to "1 Alignment" to confirm the graph shows a two data points, the most recent at 50% and the earlier one at 100% * points possible - using the canvas api, change the outcome's total points possible from 5 to 10 by updating the points on the top rating - in the LMGB, confirm that the student has a score of "6.75 / 3" - click on the student's name and view the alignments - confirm that the alignment show a score of "6.75 / 3" - hover over the three dots next to "1 Alignment" to confirm the graph shows a two data points, the most recent at 50% and the earlier one at 100% * calculation method - using the canvas api, change the calculation method to 'latest' - in the LMGB, confirm that the student has a score of "5 / 3" - click on the student's name and view the alignments - confirm that the alignment show a score of "5 / 3" - hover over the three dots next to "1 Alignment" to confirm the graph shows a two data points, the most recent at 50% and the earlier one at 100% * mastery points - using the canvas api, change the mastery points to 10 - in the LMGB, confirm that the student no longer has mastery - click on the student's name and view the alignments - confirm the student no longer displays mastery * 0 points possible - using the canvas api, set all ratings to have 0 points - in the LMGB, confirm that the student has a score of "5 / 10" - click on the student's name and view the alignments - confirm that the alignment show a score of "5 / 10" - hover over the three dots next to "1 Alignment" to confirm the graph shows a two data points, the most recent at 50% and the earlier one at 100% Change-Id: I8aea02a1ece158742cafc969d9c2fcd79a3259c2 Reviewed-on: https://gerrit.instructure.com/141469 Tested-by: Jenkins Reviewed-by: Matt Berns <mberns@instructure.com> Reviewed-by: Michael Brewer-Davis <mbd@instructure.com> QA-Review: Leo Abner <rabner@instructure.com> Product-Review: Sidharth Oberoi <soberoi@instructure.com>
This commit is contained in:
parent
ae690d09f6
commit
6b21b822fc
|
@ -49,6 +49,10 @@ define [
|
|||
handleAdd: (model) =>
|
||||
alignment_id = model.get('links').alignment
|
||||
model.set('alignment_name', @alignments.get(alignment_id)?.get('name'))
|
||||
if model.get('points_possible') > 0
|
||||
model.set('score', model.get('points_possible') * model.get('percent'))
|
||||
else
|
||||
model.set('score', model.get('mastery_points') * model.get('percent'))
|
||||
|
||||
parse: (response) ->
|
||||
@alignments ?= new Backbone.Collection([])
|
||||
|
|
|
@ -144,10 +144,16 @@ define [
|
|||
).value()
|
||||
|
||||
masteryPercentage: ->
|
||||
(@model.get('mastery_points') / @model.get('points_possible')) * 100
|
||||
if @model.get('points_possible') > 0
|
||||
(@model.get('mastery_points') / @model.get('points_possible')) * 100
|
||||
else
|
||||
100
|
||||
|
||||
percentageFor: (score) ->
|
||||
((score / @model.get('points_possible')) * 100)
|
||||
if @model.get('points_possible') > 0
|
||||
((score / @model.get('points_possible')) * 100)
|
||||
else
|
||||
((score / @model.get('mastery_points')) * 100)
|
||||
|
||||
xValue: (point) =>
|
||||
@x(point.x)
|
||||
|
|
|
@ -57,7 +57,6 @@ class LearningOutcome < ActiveRecord::Base
|
|||
validates :short_description, :workflow_state, presence: true
|
||||
sanitize_field :description, CanvasSanitize::SANITIZE
|
||||
validate :validate_calculation_int
|
||||
validate :validate_text_only_changes_when_assessed
|
||||
|
||||
set_policy do
|
||||
# managing a contextual outcome requires manage_outcomes on the outcome's context
|
||||
|
@ -107,33 +106,6 @@ class LearningOutcome < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def validate_text_only_changes_when_assessed
|
||||
if persisted? && assessed?
|
||||
if criterion_non_text_fields_changed? || (self.changes.keys - %w{data description short_description display_name}).any?
|
||||
self.errors.add(:base, t("This outcome has been used to assess a student. Only text fields can be updated"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def criterion_non_text_fields_changed?
|
||||
return false unless self.data_changed?
|
||||
old_criterion = (self.data_was && self.data_was[:rubric_criterion]) || {}
|
||||
return true if self.rubric_criterion.symbolize_keys.except(:description, :ratings) != old_criterion.symbolize_keys.except(:description, :ratings)
|
||||
|
||||
new_ratings = self.rubric_criterion[:ratings] || []
|
||||
old_ratings = old_criterion[:ratings] || []
|
||||
return true if new_ratings.count != old_ratings.count
|
||||
|
||||
non_description_changed = false
|
||||
new_ratings.each_with_index do |new_rating, idx|
|
||||
if new_rating.symbolize_keys.except(:description) != old_ratings[idx].symbolize_keys.except(:description)
|
||||
non_description_changed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
return non_description_changed
|
||||
end
|
||||
|
||||
def self.valid_calculation_method?(method)
|
||||
CALCULATION_METHODS.keys.include?(method)
|
||||
end
|
||||
|
|
|
@ -725,7 +725,7 @@ describe "Outcomes API", type: :request do
|
|||
}
|
||||
end
|
||||
|
||||
it "should not allow updating calculation method after being used for assessing" do
|
||||
it "should allow updating calculation method after being used for assessing" do
|
||||
expect(@outcome).to be_assessed
|
||||
expect(@outcome.calculation_method).to eq('decaying_average')
|
||||
|
||||
|
@ -737,16 +737,16 @@ describe "Outcomes API", type: :request do
|
|||
{ :title => "New Title",
|
||||
:description => "New Description",
|
||||
:vendor_guid => "vendorguid9000",
|
||||
:calculation_method => "n_mastery" },
|
||||
:calculation_method => "highest" },
|
||||
{},
|
||||
{ :expected_status => 400 })
|
||||
{ :expected_status => 200 })
|
||||
|
||||
@outcome.reload
|
||||
expect(json).not_to eq(outcome_json) # it should be filled with an error
|
||||
expect(@outcome.calculation_method).to eq('decaying_average')
|
||||
expect(json).to eq(outcome_json)
|
||||
expect(@outcome.calculation_method).to eq('highest')
|
||||
end
|
||||
|
||||
it "should not allow updating calculation int after being used for assessing" do
|
||||
it "should allow updating calculation int after being used for assessing" do
|
||||
expect(@outcome).to be_assessed
|
||||
expect(@outcome.calculation_method).to eq('decaying_average')
|
||||
expect(@outcome.calculation_int).to eq(62)
|
||||
|
@ -761,12 +761,12 @@ describe "Outcomes API", type: :request do
|
|||
:vendor_guid => "vendorguid9000",
|
||||
:calculation_int => "59" },
|
||||
{},
|
||||
{ :expected_status => 400 })
|
||||
{ :expected_status => 200 })
|
||||
|
||||
@outcome.reload
|
||||
expect(json).not_to eq(outcome_json) # it should be filled with an error
|
||||
expect(json).to eq(outcome_json)
|
||||
expect(@outcome.calculation_method).to eq('decaying_average')
|
||||
expect(@outcome.calculation_int).to eq(62)
|
||||
expect(@outcome.calculation_int).to eq(59)
|
||||
end
|
||||
|
||||
it "should allow updating text-only fields even when assessed" do
|
||||
|
@ -808,26 +808,26 @@ describe "Outcomes API", type: :request do
|
|||
expect(@outcome2.rubric_criterion[:ratings]).to eq new_ratings
|
||||
end
|
||||
|
||||
it "should not allow updating rating points" do
|
||||
it "should allow updating rating points" do
|
||||
new_ratings = [{ description: "some new desc1", points: 5 },
|
||||
{ description: "some new desc2", points: 3 }]
|
||||
json = api_call(:put, "/api/v1/outcomes/#{@outcome2.id}",
|
||||
{ :controller => 'outcomes_api', :action => 'update',
|
||||
:id => @outcome2.id.to_s, :format => 'json' },
|
||||
{ :ratings => new_ratings },
|
||||
{}, { :expected_status => 400 })
|
||||
{}, { :expected_status => 200 })
|
||||
@outcome2.reload
|
||||
expect(@outcome2.rubric_criterion[:ratings]).to_not eq new_ratings
|
||||
expect(@outcome2.rubric_criterion[:ratings]).to eq new_ratings
|
||||
end
|
||||
|
||||
it "should not allow updating mastery points" do
|
||||
it "should allow updating mastery points" do
|
||||
json = api_call(:put, "/api/v1/outcomes/#{@outcome2.id}",
|
||||
{ :controller => 'outcomes_api', :action => 'update',
|
||||
:id => @outcome2.id.to_s, :format => 'json' },
|
||||
{ :mastery_points => 7 },
|
||||
{}, { :expected_status => 400 })
|
||||
{}, { :expected_status => 200 })
|
||||
@outcome2.reload
|
||||
expect(@outcome2.rubric_criterion[:mastery_points]).to_not eq 7
|
||||
expect(@outcome2.rubric_criterion[:mastery_points]).to eq 7
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,12 @@ QUnit.module('OutcomeResultCollectionSpec', {
|
|||
mastery_points: 8,
|
||||
points_possible: 10
|
||||
})
|
||||
this.outcome2 = new Outcome({
|
||||
mastery_points: 8,
|
||||
points_possible: 0
|
||||
})
|
||||
this.outcomeResultCollection = new OutcomeResultCollection([], {outcome: this.outcome})
|
||||
this.outcomeResultCollection2 = new OutcomeResultCollection([], {outcome: this.outcome2})
|
||||
this.alignmentName = 'First Alignment Name'
|
||||
this.alignmentName2 = 'Second Alignment Name'
|
||||
this.alignmentName3 = 'Third Alignment Name'
|
||||
|
@ -39,7 +44,8 @@ QUnit.module('OutcomeResultCollectionSpec', {
|
|||
outcome_results: [
|
||||
{
|
||||
submitted_or_assessed_at: tz.parse('2015-04-24T19:27:54Z'),
|
||||
links: {alignment: 'alignment_1'}
|
||||
links: {alignment: 'alignment_1'},
|
||||
percent: 0.4
|
||||
}
|
||||
],
|
||||
linked: {
|
||||
|
@ -108,6 +114,15 @@ test('#handleAdd', function() {
|
|||
ok(this.outcomeResultCollection.add(this.response.outcome_results[0]))
|
||||
ok(this.outcomeResultCollection.length, 1)
|
||||
equal(this.alignmentName, this.outcomeResultCollection.first().get('alignment_name'))
|
||||
equal(this.outcomeResultCollection.first().get('score'), 4.0)
|
||||
})
|
||||
|
||||
test('#handleAdd 0 points_possible', function() {
|
||||
equal(this.outcomeResultCollection2.length, 0, 'precondition')
|
||||
this.outcomeResultCollection2.alignments = new Backbone.Collection(this.response.linked.alignments)
|
||||
ok(this.outcomeResultCollection2.add(this.response.outcome_results[0]))
|
||||
ok(this.outcomeResultCollection2.length, 1)
|
||||
equal(this.outcomeResultCollection2.first().get('score'), 3.2)
|
||||
})
|
||||
|
||||
test('#handleSort', function() {
|
||||
|
|
Loading…
Reference in New Issue