Disallow updating proficiency attributes

closes OUT-4125

flag=account_level_mastery_scales

At the API level, disallow updating an outcome's
proficiency attributes, since these will be managed
at the context level. Additionally, when editing
an outcome in the UI, exclude these attributes from
the Backbone model so they are not included when
persisting changes to the API.

test plan:
  - enable the feature flag
  - create account mastery scale
  - create an account outcome
  - reload the page
  - confirm that editing the outcome does
    not error when saving
  - disable the feature flag
  - confirm that editing the outcome does
    not error when saving

Change-Id: I647049372ae46d31a07f9fa34056cc9d4315047f
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254896
Reviewed-by: Pat Renner <prenner@instructure.com>
Reviewed-by: Michael Brewer-Davis <mbd@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Chrystal Langston <chrystal.langston@instructure.com>
Product-Review: Jody Sailor
This commit is contained in:
Augusto Callejas 2020-12-10 19:22:05 -10:00
parent b1bd9d1955
commit 63f3256897
4 changed files with 81 additions and 2 deletions

View File

@ -104,3 +104,12 @@ export default class Outcome extends Backbone.Model
when 'add' then @outcomeGroup.outcomes_url
when 'edit' then @get 'url'
when 'delete' then @outcomeLink.url
save: (data, saveOpts) ->
if ENV.ACCOUNT_LEVEL_MASTERY_SCALES
@unset 'mastery_points'
@unset 'points_possible'
@unset 'ratings'
@unset 'calculation_method'
@unset 'calculation_int'
super

View File

@ -266,6 +266,21 @@ class OutcomesApiController < ApplicationController
def update
return unless authorized_action(@outcome, @current_user, :update)
if @domain_root_account.feature_enabled?(:account_level_mastery_scales)
error_msg = nil
if params[:mastery_points]
error_msg = t('Individual outcome mastery points cannot be modified.')
elsif params[:ratings]
error_msg = t('Individual outcome ratings cannot be modified.')
elsif params[:calculation_method] || params[:calculation_int]
error_msg = t('Individual outcome calculation values cannot be modified.')
end
if error_msg
render json: { error: error_msg }, status: :forbidden
return
end
end
update_outcome_criterion(@outcome) if params[:mastery_points] || params[:ratings]
if @outcome.update(params.permit(*DIRECT_PARAMS))
render :json => outcome_json(@outcome, @current_user, session, {context: @context})

View File

@ -727,6 +727,50 @@ describe "Outcomes API", type: :request do
end
end
end
context "with account_level_mastery_scales enabled" do
before do
@outcome.context.root_account.set_feature_flag!(:account_level_mastery_scales, 'on')
end
it "should fail when updating mastery points" do
api_call(:put, "/api/v1/outcomes/#{@outcome.id}",
{ :controller => 'outcomes_api',
:action => 'update',
:id => @outcome.id.to_s,
:format => 'json' },
{ :mastery_points => 5 })
assert_forbidden
expect(JSON.parse(response.body)['error']).to eq 'Individual outcome mastery points cannot be modified.'
end
it "should fail when updating ratings" do
api_call(:put, "/api/v1/outcomes/#{@outcome.id}",
{ :controller => 'outcomes_api',
:action => 'update',
:id => @outcome.id.to_s,
:format => 'json' },
{ :ratings => [
{ :points => 10, :description => "Exceeds Expectations" },
{ :points => 5, :description => "Meets Expectations" },
{ :points => 0, :description => "Does Not Meet Expectations" }
]})
assert_forbidden
expect(JSON.parse(response.body)['error']).to eq 'Individual outcome ratings cannot be modified.'
end
it "should fail when updating calculation values" do
api_call(:put, "/api/v1/outcomes/#{@outcome.id}",
{ :controller => 'outcomes_api',
:action => 'update',
:id => @outcome.id.to_s,
:format => 'json' },
{ :calculation_method => 'decaying_average',
:calculation_int => 65 })
assert_forbidden
expect(JSON.parse(response.body)['error']).to eq 'Individual outcome calculation values cannot be modified.'
end
end
end
end

View File

@ -67,13 +67,13 @@ test('CanManage returns true for an account outcome on the course level', functi
test('default calculation method settings not set if calculation_method exists', function() {
const spy = sandbox.spy(Outcome.prototype, 'setDefaultCalcSettings')
const outcome = new Outcome(this.importedOutcome, {parse: true})
new Outcome(this.importedOutcome, {parse: true})
ok(!spy.called)
})
test('default calculation method settings set if calculation_method is null', function() {
const spy = sandbox.spy(Outcome.prototype, 'setDefaultCalcSettings')
const outcome = new Outcome(this.courseOutcome, {parse: true})
new Outcome(this.courseOutcome, {parse: true})
ok(spy.calledOnce)
})
@ -166,3 +166,14 @@ test('it uses the ENV.MASTERY_SCALES ratings', function() {
equal(outcome.get('mastery_points'), 3)
equal(outcome.get('points_possible'), 4)
})
test('ignores proficiency attributes during saving', function() {
const outcome = new Outcome(this.importedOutcome, {parse: true})
sinon.stub(outcome, 'url').returns('fake-url')
outcome.save({}, {})
equal(outcome.get('mastery_points'), null)
equal(outcome.get('points_possible'), null)
equal(outcome.get('ratings'), null)
equal(outcome.get('calculation_method'), null)
equal(outcome.get('calculation_int'), null)
})