Create ModuleAssignmentOverrides#index
List overrides that apply to a module, including the ids and names of students/sections that are targeted by the override. closes LF-651 flag = differentiated_modules Test plan: - In a course, create a module - Create some assignment overrides for the module targeting both sections and students (see module_assignment_overrides_controller_spec lines 29-34) - GET /api/v1/courses/:course_id/modules/:module_id/assignment_overrides - Expect a list of overrides with section/student names and IDs - Make the request as a student - Expect unauthorized - Make a request with bad course or module IDs - Expect 404 - Disable the flag and make the request - Expect 404 Change-Id: Ifdc812812734dcf58c573775cbb92ad21e4131c0 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/327379 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Robin Kuss <rkuss@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Jackson Howe <jackson.howe@instructure.com>
This commit is contained in:
parent
cac04b454e
commit
280e5a0d6d
|
@ -0,0 +1,116 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# Copyright (C) 2023 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# @API Modules
|
||||
# @subtopic Module Assignment Overrides
|
||||
#
|
||||
# If any active AssignmentOverrides exist on a ContextModule, then only students who have an
|
||||
# applicable override can access the module and are assigned its items. AssignmentOverrides can
|
||||
# be created for a (group of) student(s) or a section. *This module overrides feature is still
|
||||
# under development and is not yet enabled.*
|
||||
#
|
||||
# @model ModuleAssignmentOverride
|
||||
# {
|
||||
# "id": "ModuleAssignmentOverride",
|
||||
# "properties": {
|
||||
# "id": {
|
||||
# "description": "the ID of the assignment override",
|
||||
# "example": 4355,
|
||||
# "type": "integer"
|
||||
# },
|
||||
# "context_module_id": {
|
||||
# "description": "the ID of the module the override applies to",
|
||||
# "example": 567,
|
||||
# "type": "integer"
|
||||
# },
|
||||
# "title": {
|
||||
# "description": "the title of the override",
|
||||
# "example": "Section 6",
|
||||
# "type": "string"
|
||||
# },
|
||||
# "students": {
|
||||
# "description": "an array of the override's target students (present only if the override targets an adhoc set of students)",
|
||||
# "$ref": "OverrideTarget"
|
||||
# },
|
||||
# "course_section": {
|
||||
# "description": "the override's target section (present only if the override targets a section)",
|
||||
# "$ref": "OverrideTarget"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# @model OverrideTarget
|
||||
# {
|
||||
# "id": "OverrideTarget",
|
||||
# "properties": {
|
||||
# "id": {
|
||||
# "description": "the ID of the user or section that the override is targeting",
|
||||
# "example": 7,
|
||||
# "type": "integer"
|
||||
# },
|
||||
# "name": {
|
||||
# "description": "the name of the user or section that the override is targeting",
|
||||
# "example": "Section 6",
|
||||
# "type": "string"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
class ModuleAssignmentOverridesController < ApplicationController
|
||||
include Api::V1::ModuleAssignmentOverride
|
||||
|
||||
before_action :require_feature_flag # remove when differentiated_modules flag is removed
|
||||
before_action :require_user
|
||||
before_action :require_context
|
||||
before_action :check_authorized_action
|
||||
before_action :require_context_module
|
||||
|
||||
# @API List a module's overrides
|
||||
#
|
||||
# Returns a paginated list of AssignmentOverrides that apply to the ContextModule.
|
||||
#
|
||||
# Note: this API is still under development and will not function until the feature is enabled.
|
||||
#
|
||||
# @example_request
|
||||
# curl https://<canvas>/api/v1/courses/:course_id/modules/:context_module_id/assignment_overrides \
|
||||
# -H 'Authorization: Bearer <token>'
|
||||
#
|
||||
# @returns [ModuleAssignmentOverride]
|
||||
def index
|
||||
GuardRail.activate(:secondary) do
|
||||
overrides = @context_module.assignment_overrides.active
|
||||
paginated_overrides = Api.paginate(overrides, self, api_v1_module_assignment_overrides_index_url)
|
||||
render json: module_assignment_overrides_json(paginated_overrides, @current_user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_feature_flag
|
||||
not_found unless Account.site_admin.feature_enabled? :differentiated_modules
|
||||
end
|
||||
|
||||
def check_authorized_action
|
||||
render_unauthorized_action unless @context.grants_any_right?(@current_user, :manage_content, :manage_course_content_edit)
|
||||
end
|
||||
|
||||
def require_context_module
|
||||
@context_module = @context.context_modules.not_deleted.find(params[:context_module_id])
|
||||
end
|
||||
end
|
|
@ -333,7 +333,7 @@ class AssignmentOverride < ActiveRecord::Base
|
|||
return Enrollment.none if overrides.empty? || user.nil?
|
||||
|
||||
override = overrides.first
|
||||
(override.assignment || override.quiz).context.enrollments_visible_to(user)
|
||||
(override.assignment || override.quiz || override.context_module).context.enrollments_visible_to(user)
|
||||
end
|
||||
|
||||
OVERRIDDEN_DATES = %i[due_at unlock_at lock_at].freeze
|
||||
|
|
|
@ -1948,6 +1948,10 @@ CanvasRails::Application.routes.draw do
|
|||
post "courses/:course_id/modules/items/:id/duplicate", action: :duplicate, as: :course_context_module_item_duplicate
|
||||
end
|
||||
|
||||
scope(controller: :module_assignment_overrides) do
|
||||
get "courses/:course_id/modules/:context_module_id/assignment_overrides", action: :index, as: "module_assignment_overrides_index"
|
||||
end
|
||||
|
||||
scope(controller: "quizzes/quiz_assignment_overrides") do
|
||||
get "courses/:course_id/quizzes/assignment_overrides", action: :index, as: "course_quiz_assignment_overrides"
|
||||
get "courses/:course_id/new_quizzes/assignment_overrides", action: :new_quizzes, as: "course_new_quizzes_assignment_overrides"
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# Copyright (C) 2023 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module Api::V1::ModuleAssignmentOverride
|
||||
include Api::V1::Json
|
||||
|
||||
FIELDS = %i[id context_module_id title].freeze
|
||||
|
||||
def module_assignment_overrides_json(overrides, user)
|
||||
adhoc_overrides = overrides.select { |override| override.set_type == "ADHOC" }
|
||||
visible_users_ids = ::AssignmentOverride.visible_enrollments_for(overrides.compact, user).select(:user_id)
|
||||
if adhoc_overrides.any? { |override| !override.preloaded_student_ids }
|
||||
AssignmentOverrideApplicator.preload_student_ids_for_adhoc_overrides(adhoc_overrides, visible_users_ids)
|
||||
end
|
||||
user_names = User.where(id: adhoc_overrides.flat_map(&:preloaded_student_ids)).pluck(:id, :name).to_h
|
||||
overrides.map { |override| module_assignment_override_json(override, user_names) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def module_assignment_override_json(override, user_names)
|
||||
api_json(override, @current_user, session, only: FIELDS).tap do |json|
|
||||
case override.set_type
|
||||
when "ADHOC"
|
||||
json[:students] = override.preloaded_student_ids.map { |user_id| { id: user_id, name: user_names[user_id] } }
|
||||
when "CourseSection"
|
||||
json[:course_section] = { id: override.set.id, name: override.set.name }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,118 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
#
|
||||
# Copyright (C) 2023 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
describe ModuleAssignmentOverridesController do
|
||||
before :once do
|
||||
Account.site_admin.enable_feature!(:differentiated_modules)
|
||||
course_with_teacher(active_all: true, course_name: "Awesome Course")
|
||||
@student1 = student_in_course(active_all: true, name: "Student 1").user
|
||||
@student2 = student_in_course(active_all: true, name: "Student 2").user
|
||||
@student3 = student_in_course(active_all: true, name: "Student 3").user
|
||||
@module1 = @course.context_modules.create!(name: "Module 1")
|
||||
@section_override1 = @module1.assignment_overrides.create!(set_type: "CourseSection", set_id: @course.course_sections.first)
|
||||
@adhoc_override1 = @module1.assignment_overrides.create!(set_type: "ADHOC")
|
||||
@adhoc_override1.assignment_override_students.create!(user: @student1)
|
||||
@adhoc_override1.assignment_override_students.create!(user: @student2)
|
||||
@adhoc_override2 = @module1.assignment_overrides.create!(set_type: "ADHOC")
|
||||
@adhoc_override2.assignment_override_students.create!(user: @student3)
|
||||
end
|
||||
|
||||
before do
|
||||
user_session(@teacher)
|
||||
end
|
||||
|
||||
describe "GET 'index'" do
|
||||
it "returns a list of module assignment overrides" do
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
|
||||
expect(response).to be_successful
|
||||
json = json_parse(response.body)
|
||||
expect(json.length).to be 3
|
||||
|
||||
expect(json[0]["id"]).to be @section_override1.id
|
||||
expect(json[0]["context_module_id"]).to be @module1.id
|
||||
expect(json[0]["title"]).to eq "Awesome Course"
|
||||
expect(json[0]["course_section"]["id"]).to eq @course.course_sections.first.id
|
||||
expect(json[0]["course_section"]["name"]).to eq "Awesome Course"
|
||||
|
||||
expect(json[1]["id"]).to be @adhoc_override1.id
|
||||
expect(json[1]["context_module_id"]).to be @module1.id
|
||||
expect(json[1]["title"]).to eq "No Title"
|
||||
expect(json[1]["students"].length).to eq 2
|
||||
expect(json[1]["students"][0]["id"]).to eq @student1.id
|
||||
expect(json[1]["students"][0]["name"]).to eq "Student 1"
|
||||
expect(json[1]["students"][1]["id"]).to eq @student2.id
|
||||
expect(json[1]["students"][1]["name"]).to eq "Student 2"
|
||||
|
||||
expect(json[2]["id"]).to be @adhoc_override2.id
|
||||
expect(json[2]["context_module_id"]).to be @module1.id
|
||||
expect(json[2]["title"]).to eq "No Title"
|
||||
expect(json[2]["students"].length).to eq 1
|
||||
expect(json[2]["students"][0]["id"]).to eq @student3.id
|
||||
expect(json[2]["students"][0]["name"]).to eq "Student 3"
|
||||
end
|
||||
|
||||
it "does not include deleted assignment overrides" do
|
||||
@adhoc_override2.update!(workflow_state: "deleted")
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
|
||||
expect(response).to be_successful
|
||||
json = json_parse(response.body)
|
||||
expect(json.pluck("id")).to contain_exactly(@section_override1.id, @adhoc_override1.id)
|
||||
end
|
||||
|
||||
it "returns 404 if the course doesn't exist" do
|
||||
get :index, params: { course_id: 0, context_module_id: @module1.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 404 if the module is deleted or nonexistent" do
|
||||
@module1.update!(workflow_state: "deleted")
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
expect(response).to be_not_found
|
||||
|
||||
@module1.assignment_override_students.each(&:delete)
|
||||
@module1.assignment_overrides.each(&:delete)
|
||||
@module1.delete
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 404 if the module is in a different course" do
|
||||
course2 = course_with_teacher(active_all: true, user: @teacher).course
|
||||
course2.context_modules.create!
|
||||
get :index, params: { course_id: course2, context_module_id: @module1.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 404 if the differentiated_modules flag is disabled" do
|
||||
Account.site_admin.disable_feature!(:differentiated_modules)
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns unauthorized if the user doesn't have manage_course_content_edit permission" do
|
||||
student = student_in_course.user
|
||||
user_session(student)
|
||||
get :index, params: { course_id: @course.id, context_module_id: @module1.id }
|
||||
expect(response).to be_unauthorized
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue