add include[]=can_edit to assignments index

test plan:
 - render API docs and note include[]=can_edit
 - set up some assignments and assignment overrides
   including dates inside and outside closed
   grading periods
 - use this endpoint, including can_edit and all_dates
 - as a teacher,
   - ensure can_edit is true for assignments and dates
     in the absence of grading periods or moderated grading
   - ensure can_edit is false for a date that lands in
     a closed grading period (still true for the
     assignment, however)
   - ensure can_edit is false for an assignment and all
     dates for a moderated grading assignment where the
     teacher lacks "select final grade" permission and
     isn't the moderator
  - as a student,
   - ensure can_edit is false everywhere
  - as an account administrator,
   - ensure can_edit is true everywhere
 - also test the can_edit include in
  - assignment show
  - assignment group index w/include[]=assignments

flag=none
closes LA-861

Change-Id: I7c0df7a0071cfd2bdb97e09cf4b77800de962d54
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/232004
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Anju Reddy <areddy@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
Reviewed-by: James Williams <jamesw@instructure.com>
Reviewed-by: Jon Willesen <jonw+gerrit@instructure.com>
This commit is contained in:
Jeremy Stanley 2020-03-27 16:56:56 -06:00
parent 2a8b3c95a4
commit 1c62e34641
7 changed files with 123 additions and 36 deletions

View File

@ -101,8 +101,8 @@ class AssignmentGroupsController < ApplicationController
# Returns the paginated list of assignment groups for the current context.
# The returned groups are sorted by their position field.
#
# @argument include[] [String, "assignments"|"discussion_topic"|"all_dates"|"assignment_visibility"|"overrides"|"submission"|"observed_users"]
# Associations to include with the group. "discussion_topic", "all_dates"
# @argument include[] [String, "assignments"|"discussion_topic"|"all_dates"|"assignment_visibility"|"overrides"|"submission"|"observed_users"|"can_edit"]
# Associations to include with the group. "discussion_topic", "all_dates", "can_edit",
# "assignment_visibility" & "submission" are only valid if "assignments" is also included.
# The "assignment_visibility" option additionally requires that the Differentiated Assignments course feature be turned on.
# If "observed_users" is passed along with "assignments" and "submission", submissions for observed users will also be included as an array.

View File

@ -627,10 +627,14 @@ class AssignmentsApiController < ApplicationController
# @API List assignments
# Returns the paginated list of assignments for the current course or assignment group.
# @argument include[] [String, "submission"|"assignment_visibility"|"all_dates"|"overrides"|"observed_users"]
# Associations to include with the assignment. The "assignment_visibility" option
# requires that the Differentiated Assignments course feature be turned on. If
# "observed_users" is passed, submissions for observed users will also be included as an array.
# @argument include[] [String, "submission"|"assignment_visibility"|"all_dates"|"overrides"|"observed_users"|"can_edit"]
# Optional information to include with each assignment:
# submission:: The current user's current +Submission+
# assignment_visibility:: An array of ids of students who can see the assignment
# all_dates:: An array of +AssignmentDate+ structures, one for each override, and also a +base+ if the assignment has an "Everyone" / "Everyone Else" date
# overrides:: An array of +AssignmentOverride+ structures
# observed_users:: An array of submissions for observed users
# can_edit:: an extra Boolean value will be included with each +Assignment+ (and +AssignmentDate+ if +all_dates+ is supplied) to indicate whether the caller can edit the assignment or date. Moderated grading and closed grading periods may restrict a user's ability to edit an assignment.
# @argument search_term [String]
# The partial title of the assignments to match and return.
# @argument override_assignment_dates [Boolean]
@ -827,7 +831,8 @@ class AssignmentsApiController < ApplicationController
include_all_dates: include_all_dates,
bucket: params[:bucket],
include_overrides: include_override_objects,
preloaded_user_content_attachments: preloaded_attachments
preloaded_user_content_attachments: preloaded_attachments,
include_can_edit: include_params.include?('can_edit')
)
end
hashes
@ -836,7 +841,7 @@ class AssignmentsApiController < ApplicationController
# @API Get a single assignment
# Returns the assignment with the given id.
# @argument include[] [String, "submission"|"assignment_visibility"|"overrides"|"observed_users"]
# @argument include[] [String, "submission"|"assignment_visibility"|"overrides"|"observed_users"|"can_edit"]
# Associations to include with the assignment. The "assignment_visibility" option
# requires that the Differentiated Assignments course feature be turned on. If
# "observed_users" is passed, submissions for observed users will also be included.
@ -879,7 +884,8 @@ class AssignmentsApiController < ApplicationController
include_visibility: include_visibility,
needs_grading_count_by_section: needs_grading_count_by_section,
include_all_dates: include_all_dates,
include_overrides: include_override_objects
include_overrides: include_override_objects,
include_can_edit: included_params.include?('can_edit')
}
result_json = if use_quiz_json?

View File

@ -120,7 +120,8 @@ module Api::V1::Assignment
override_dates: true,
needs_grading_count_by_section: false,
exclude_response_fields: [],
include_planner_override: false
include_planner_override: false,
include_can_edit: false
)
if opts[:override_dates] && !assignment.new_record?
@ -314,6 +315,16 @@ module Api::V1::Assignment
end
end
if opts[:include_can_edit]
can_edit_assignment = assignment.user_can_update?(user, session)
hash['can_edit'] = can_edit_assignment
hash['all_dates']&.each do |date_hash|
in_closed_grading_period = date_in_closed_grading_period?(date_hash['due_at'])
date_hash['in_closed_grading_period'] = in_closed_grading_period
date_hash['can_edit'] = can_edit_assignment && (!in_closed_grading_period || !constrained_by_grading_periods?)
end
end
if opts[:include_module_ids]
modulable = case assignment.submission_types
when 'online_quiz' then assignment.quiz

View File

@ -73,6 +73,7 @@ module Api::V1::AssignmentGroup
json = assignment_json(assignment, user, session,
include_discussion_topic: includes.include?('discussion_topic'),
include_all_dates: includes.include?('all_dates'),
include_can_edit: includes.include?('can_edit'),
include_module_ids: includes.include?('module_ids'),
include_grades_published: includes.include?('grades_published'),
override_dates: opts[:override_assignment_dates],

View File

@ -16,10 +16,14 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
module SubmittablesGradingPeriodProtection
def constrained_by_grading_periods?
grading_periods? && !current_user_is_context_admin?
end
def grading_periods_allow_submittable_create?(submittable, submittable_params, flash_message: false)
apply_grading_params(submittable, submittable_params)
return true unless submittable.graded?
return true unless grading_periods? && !current_user_is_context_admin?
return true unless constrained_by_grading_periods?
return true if submittable_params[:only_visible_to_overrides]
submittable.due_at = submittable_params[:due_at]
@ -36,7 +40,7 @@ module SubmittablesGradingPeriodProtection
submittable_params[:only_visible_to_overrides] if submittable_params.key?(:only_visible_to_overrides)
submittable.due_at = submittable_params[:due_at] if submittable_params.key?(:due_at)
return true unless submittable.only_visible_to_overrides_changed? || due_at_changed?(submittable)
return true unless grading_periods? && !current_user_is_context_admin?
return true unless constrained_by_grading_periods?
in_closed_grading_period = date_in_closed_grading_period?(submittable.due_at_was)
@ -59,7 +63,7 @@ module SubmittablesGradingPeriodProtection
end
def grading_periods_allow_assignment_overrides_batch_create?(submittable, overrides, flash_message: false)
return true unless grading_periods? && !current_user_is_context_admin?
return true unless constrained_by_grading_periods?
return true unless overrides.any? {|override| date_in_closed_grading_period?(override[:due_at])}
apply_error(submittable, :due_at, ERROR_MESSAGES[:set_override_due_at_in_closed], flash_message)
@ -67,14 +71,14 @@ module SubmittablesGradingPeriodProtection
end
def grading_periods_allow_assignment_overrides_batch_update?(submittable, prepared_batch, flash_message: false)
return true unless grading_periods? && !current_user_is_context_admin?
return true unless constrained_by_grading_periods?
can_create_overrides?(submittable, prepared_batch[:overrides_to_create], flash_message: flash_message) &&
can_update_overrides?(submittable, prepared_batch[:overrides_to_update], flash_message: flash_message) &&
can_delete_overrides?(submittable, prepared_batch[:overrides_to_delete], flash_message: flash_message)
end
def grading_periods_allow_assignment_override_update?(override)
return true unless grading_periods? && !current_user_is_context_admin?
return true unless constrained_by_grading_periods?
return true unless override.changed?
if date_in_closed_grading_period?(override.due_at_was)
@ -90,6 +94,10 @@ module SubmittablesGradingPeriodProtection
true
end
def date_in_closed_grading_period?(date)
GradingPeriodHelper.date_in_closed_grading_period?(date, context_grading_periods)
end
private
def due_at_changed?(submittable)
@ -151,10 +159,6 @@ module SubmittablesGradingPeriodProtection
@context_grading_periods ||= GradingPeriod.for(@context)
end
def date_in_closed_grading_period?(date)
GradingPeriodHelper.date_in_closed_grading_period?(date, context_grading_periods)
end
def apply_error(submittable, attribute, message, flash_message)
submittable.errors.add(attribute, message)
flash[:error] = message if flash_message

View File

@ -480,10 +480,10 @@ describe AssignmentGroupsController, type: :request do
a1.reload
json = api_call(:get,
"/api/v1/courses/#{@course.id}/assignment_groups.json?include[]=assignments&include[]=all_dates",
"/api/v1/courses/#{@course.id}/assignment_groups.json?include[]=assignments&include[]=all_dates&include[]=can_edit",
{ controller: 'assignment_groups', action: 'index',
format: 'json', course_id: @course.id.to_s,
include: ['assignments', 'all_dates'] })
include: ['assignments', 'all_dates', 'can_edit'] })
expected = [
{
@ -496,8 +496,8 @@ describe AssignmentGroupsController, type: :request do
'integration_data' => {},
'sis_source_id' => nil,
'assignments' => [
controller.assignment_json(a1, @user,session, include_all_dates: true).as_json,
controller.assignment_json(a2, @user,session, include_all_dates: true).as_json
controller.assignment_json(a1, @user,session, include_all_dates: true, include_can_edit: true).as_json,
controller.assignment_json(a2, @user,session, include_all_dates: true, include_can_edit: true).as_json
]
}
]

View File

@ -5318,19 +5318,6 @@ describe AssignmentsApiController, type: :request do
api_bulk_update(@course, [], expected_status: 401)
end
it "disallows editing moderated assignments if you're not the moderator" do
@course.account.role_overrides.create!(permission: :select_final_grade, enabled: false, role: ta_role)
ta_in_course(:active_all => true)
api_bulk_update(@course, [{'id' => @a0.id, 'all_dates' => []}])
@a0.moderated_grading = true
@a0.final_grader_id = @teacher
@a0.grader_count = 1
@a0.save!
api_bulk_update(@course, [{'id' => @a0.id, 'all_dates' => []}], expected_status: 401)
end
it "expects an array of assignments" do
api_bulk_update(@course, {}, expected_status: 400)
end
@ -5518,6 +5505,84 @@ describe AssignmentsApiController, type: :request do
expect_any_instance_of(Course).to receive(:recompute_student_scores_without_send_later).once
api_bulk_update(@course, data)
end
it "sets can_edit on each date if requested" do
json = api_get_assignments_index_from_course(@course, include: %w(all_dates can_edit))
a0_json = json.detect { |a| a['id'] == @a0.id }
expect(a0_json['can_edit']).to eq true
expect(a0_json['all_dates'].map { |d| d['can_edit'] }).to eq [true]
expect(a0_json['all_dates'].map { |d| d['in_closed_grading_period'] }).to eq [false]
a1_json = json.detect { |a| a['id'] == @a1.id }
expect(a1_json['can_edit']).to eq true
expect(a1_json['all_dates'].map { |d| d['can_edit'] }).to eq [false]
expect(a1_json['all_dates'].map { |d| d['in_closed_grading_period'] }).to eq [true]
a2_json = json.detect { |a| a['id'] == @a2.id }
expect(a2_json['can_edit']).to eq true
ao0_json = a2_json['all_dates'].detect { |ao| ao['id'] == @ao0.id }
expect(ao0_json['can_edit']).to eq false
expect(ao0_json['in_closed_grading_period']).to eq true
ao1_json = a2_json['all_dates'].detect { |ao| ao['id'] == @ao1.id }
expect(ao1_json['can_edit']).to eq true
expect(ao1_json['in_closed_grading_period']).to eq false
end
it "allows account admins to edit whatever they want" do
account_admin_user
json = api_get_assignments_index_from_course(@course, include: %w(all_dates can_edit))
a0_json = json.detect { |a| a['id'] == @a0.id }
expect(a0_json['can_edit']).to eq true
expect(a0_json['all_dates'].map { |d| d['can_edit'] }).to eq [true]
expect(a0_json['all_dates'].map { |d| d['in_closed_grading_period'] }).to eq [false]
a1_json = json.detect { |a| a['id'] == @a1.id }
expect(a1_json['can_edit']).to eq true
expect(a1_json['all_dates'].map { |d| d['can_edit'] }).to eq [true]
expect(a1_json['all_dates'].map { |d| d['in_closed_grading_period'] }).to eq [true]
a2_json = json.detect { |a| a['id'] == @a2.id }
expect(a2_json['can_edit']).to eq true
ao0_json = a2_json['all_dates'].detect { |ao| ao['id'] == @ao0.id }
expect(ao0_json['can_edit']).to eq true
expect(ao0_json['in_closed_grading_period']).to eq true
ao1_json = a2_json['all_dates'].detect { |ao| ao['id'] == @ao1.id }
expect(ao1_json['can_edit']).to eq true
expect(ao1_json['in_closed_grading_period']).to eq false
end
end
context "with moderated grading" do
before :once do
@course.account.role_overrides.create!(permission: :select_final_grade, enabled: false, role: ta_role)
ta_in_course(:active_all => true)
@a0.moderated_grading = true
@a0.final_grader_id = @teacher
@a0.grader_count = 1
@a0.save!
end
it "disallows editing moderated assignments if you're not the moderator" do
api_bulk_update(@course, [{'id' => @a0.id, 'all_dates' => []}], expected_status: 401)
api_bulk_update(@course, [{'id' => @a1.id, 'all_dates' => []}])
end
it "sets can_edit on each date if requested" do
json = api_get_assignments_index_from_course(@course, include: %w(all_dates can_edit))
a0_json = json.detect { |a| a['id'] == @a0.id }
expect(a0_json['can_edit']).to eq false
expect(a0_json['all_dates'].map { |d| d['can_edit'] }).to eq [false]
a1_json = json.detect { |a| a['id'] == @a1.id }
expect(a1_json['can_edit']).to eq true
expect(a1_json['all_dates'].map { |d| d['can_edit'] }).to eq [true]
end
end
end
end