add submitted/unsubmitted quiz users endpoint to quizzes api
Test plan: - As a teacher, create a quiz. - Visit the quizzes API using JSONAPI headers. For that quiz, "submitted_students" should be null. You should get a URL back to the "unsubmitted_students" endpoint, which you can query and find the unsubmitted students. - As a student submit the quiz. As a different student, don't submit the quiz. - As a teacher, visit the quiz show endpoint for that quiz again. You should have "submitted_students" and "unsubmitted_students" under the "links" hash. Query these APIs using those URLs. The students returned from the link that looks like "?submitted=true" - As a student, you should not see "submitted_students" or "unsubmitted_students" under the "links" hash when querying the show API in JSONAPI format - In non-jsonapi format as any user, you should not see "unsubmitted_students" or "submitted_students" closes CNVS-11687 Change-Id: I7094dfc37b0dde501e5a2c1f12ade983cd2a150a Reviewed-on: https://gerrit.instructure.com/31223 Reviewed-by: Derek DeVries <ddevries@instructure.com> QA-Review: Caleb Guanzon <cguanzon@instructure.com> Product-Review: Stanley Stuart <stanley@instructure.com> Tested-by: Jenkins <jenkins@instructure.com>
This commit is contained in:
parent
5aaf487658
commit
94f3b1bd15
|
@ -0,0 +1,178 @@
|
|||
# Copyright (C) 2014 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 Quizzes
|
||||
class QuizSubmissionUsersController < ::ApplicationController
|
||||
include Filters::Quizzes
|
||||
before_filter :require_context, :require_quiz
|
||||
# @API List of users who have or haven't submitted for a quiz
|
||||
# @beta
|
||||
#
|
||||
# @argument submitted [Optional, boolean]
|
||||
# If true, return users who have submitted the quiz. If false, return users
|
||||
# who have not submitted the quiz. If not present, returns all students for
|
||||
# the course.
|
||||
#
|
||||
# @returns QuizSubmissionUserList
|
||||
#
|
||||
# @model QuizSubmissionUserList
|
||||
#
|
||||
# {
|
||||
# "meta": {
|
||||
# "$ref": "QuizSubmissionUserListMeta",
|
||||
# "description": "contains meta information (such as pagination) for the list of users"
|
||||
# },
|
||||
# "users": {
|
||||
# "$ref": "User",
|
||||
# "description": "list of users that match the query"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# @model QuizSubmissionUserListMeta
|
||||
#
|
||||
# {
|
||||
# "pagination": {
|
||||
# "$ref": "JSONAPIPagination",
|
||||
# "description": "contains pagination information for the list of users"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# @model JSONAPIPagination
|
||||
#
|
||||
# {
|
||||
# "per_page": {
|
||||
# "type": "integer",
|
||||
# "description": "number of results per page",
|
||||
# "example": 10
|
||||
# },
|
||||
# "page": {
|
||||
# "type": "integer",
|
||||
# "description": "the current page passed as the ?page= parameter",
|
||||
# "example": 1
|
||||
# },
|
||||
# "template": {
|
||||
# "type": "string",
|
||||
# "description": "URL template for building out other paged URLs for this endpoint",
|
||||
# "example": "https://example.instructure.com/api/v1/courses/1/quizzes/1/submission_users?page={page}"
|
||||
# },
|
||||
# "page_count": {
|
||||
# "type": "integer",
|
||||
# "description": "number of pages for this collection",
|
||||
# "example": 10
|
||||
# },
|
||||
# "count": {
|
||||
# "type": "integer",
|
||||
# "description": "total number of items in this collection",
|
||||
# "example": 100
|
||||
# }
|
||||
# }
|
||||
def index
|
||||
return unless user_has_teacher_level_access?
|
||||
@users = if submitted_param?
|
||||
@users = submitted? ? submitted_users : unsubmitted_users
|
||||
else
|
||||
@users = user_finder.all_students
|
||||
end
|
||||
|
||||
@users, meta = Api.jsonapi_paginate(@users, self, index_base_url, page: params[:page])
|
||||
users_json = @users.map { |user| user_json(user, @current_user, session) }
|
||||
|
||||
render json: { meta: meta, users: users_json }
|
||||
end
|
||||
|
||||
# @API Send a message to unsubmitted or submitted users for the quiz
|
||||
# @beta
|
||||
#
|
||||
# @param conversations [QuizUserConversation] - Body and recipients to send the message to.
|
||||
#
|
||||
# @model QuizUserConversation
|
||||
#
|
||||
# {
|
||||
# "body": {
|
||||
# "type": "string",
|
||||
# "description": "message body of the conversation to be created",
|
||||
# "example": "Please take the quiz."
|
||||
# },
|
||||
# "recipients": {
|
||||
# "type": "string",
|
||||
# "description": "Who to send the message to. May be either 'submitted' or 'unsubmitted'",
|
||||
# "example": "submitted"
|
||||
# },
|
||||
# "subject": {
|
||||
# "type": "string",
|
||||
# "description": "Subject of the new Conversation created",
|
||||
# "example": "ATTN: Quiz 101 Students"
|
||||
# }
|
||||
# }
|
||||
def message
|
||||
return unless user_has_teacher_level_access?
|
||||
@conversation = Array(params[:conversations]).first
|
||||
if @conversation
|
||||
send_message
|
||||
render json: { status: t('created', 'created') }, status: :created
|
||||
else
|
||||
render json: [], status: :invalid_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def index_base_url
|
||||
if submitted_param?
|
||||
api_v1_course_quiz_submission_users_url(
|
||||
@quiz.context,
|
||||
@quiz,
|
||||
submitted: submitted? ? 'true' : 'false'
|
||||
)
|
||||
else
|
||||
api_v1_course_quiz_submission_users_url(@quiz.context, @quiz)
|
||||
end
|
||||
end
|
||||
|
||||
def submitted_param?
|
||||
params.key?(:submitted)
|
||||
end
|
||||
|
||||
def submitted?
|
||||
::Canvas::Plugin.value_to_boolean(params[:submitted])
|
||||
end
|
||||
|
||||
def submitted_users
|
||||
user_finder.submitted_students
|
||||
end
|
||||
|
||||
def unsubmitted_users
|
||||
user_finder.unsubmitted_students
|
||||
end
|
||||
|
||||
def user_finder
|
||||
@user_finder ||= Quizzes::QuizUserFinder.new(@quiz, @current_user)
|
||||
end
|
||||
|
||||
def send_message
|
||||
Quizzes::QuizUserMessager.new(
|
||||
conversation: @conversation,
|
||||
root_account_id: @domain_root_account.id,
|
||||
async: true,
|
||||
sender: @current_user,
|
||||
quiz: @quiz
|
||||
).send
|
||||
end
|
||||
|
||||
def user_has_teacher_level_access?
|
||||
authorized_action(@quiz, @current_user, [:grade, :read_statistics])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1295,4 +1295,5 @@ class Quizzes::Quiz < ActiveRecord::Base
|
|||
def self.reflection_type_name
|
||||
'quizzes:quiz'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -784,7 +784,7 @@ class Quizzes::QuizSubmission < ActiveRecord::Base
|
|||
}
|
||||
scope :for_user_ids, lambda { |user_ids| where(:user_id => user_ids) }
|
||||
scope :logged_out, where("temporary_user_code is not null")
|
||||
scope :not_settings_only, where("workflow_state<>'settings_only'")
|
||||
scope :not_settings_only, where("quiz_submissions.workflow_state<>'settings_only'")
|
||||
scope :completed, where(:workflow_state => %w(complete pending_review))
|
||||
|
||||
has_a_broadcast_policy
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
#
|
||||
# Copyright (C) 2014 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 Quizzes
|
||||
class QuizUserFinder
|
||||
extend Forwardable
|
||||
attr_reader :quiz, :user
|
||||
|
||||
def_delegators :@quiz, :context, :quiz_submissions
|
||||
|
||||
def initialize(quiz, user)
|
||||
@quiz = quiz
|
||||
@user = user
|
||||
end
|
||||
|
||||
def submitted_students
|
||||
all_students.where(id: non_preview_user_ids)
|
||||
end
|
||||
|
||||
def unsubmitted_students
|
||||
all_students.where('users.id NOT IN (?)', non_preview_user_ids)
|
||||
end
|
||||
|
||||
def all_students
|
||||
context.students_visible_to(user).order_by_sortable_name.group('users.id')
|
||||
end
|
||||
|
||||
def non_preview_quiz_submissions
|
||||
# This could optionally check for temporary_user_code<>NULL, but
|
||||
# that's not indexed and we're checking user_id anyway in the queries above.
|
||||
quiz_submissions.where('quiz_submissions.user_id IS NOT NULL')
|
||||
end
|
||||
|
||||
private
|
||||
def non_preview_user_ids
|
||||
non_preview_quiz_submissions.not_settings_only.select(:user_id)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
# Copyright (C) 2014 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 Quizzes
|
||||
class QuizUserMessager
|
||||
extend Forwardable
|
||||
attr_reader :sender, :async, :recipient_list, :conversation, :quiz
|
||||
attr_reader :root_account_id, :context_id
|
||||
|
||||
def_delegators :@user_finder,
|
||||
:submitted_students,
|
||||
:all_students,
|
||||
:unsubmitted_students
|
||||
|
||||
def initialize(options)
|
||||
@quiz = options.fetch(:quiz)
|
||||
@sender = options.fetch(:sender)
|
||||
@async = options.fetch(:async, true) ? :async : :sync
|
||||
@conversation = options.fetch(:conversation)
|
||||
@root_account_id = options.fetch(:root_account_id)
|
||||
@context_id = quiz.context_id
|
||||
@user_finder = Quizzes::QuizUserFinder.new(quiz, sender)
|
||||
end
|
||||
|
||||
def send
|
||||
ConversationBatch.generate(
|
||||
message,
|
||||
recipients,
|
||||
async,
|
||||
subject: subject,
|
||||
context_id: context_id,
|
||||
group: false
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message
|
||||
@message ||= (
|
||||
Conversation.build_message(
|
||||
sender,
|
||||
body,
|
||||
root_account_id: root_account_id
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def body
|
||||
conversation[:body]
|
||||
end
|
||||
|
||||
def subject
|
||||
conversation[:subject]
|
||||
end
|
||||
|
||||
def recipients
|
||||
list = conversation.fetch(:recipients, 'all')
|
||||
recipients = case list.to_s
|
||||
when 'unsubmitted' then unsubmitted_students
|
||||
when 'submitted' then submitted_students
|
||||
else all_students
|
||||
end
|
||||
sender.load_messageable_users(recipients.pluck(:id))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,7 +15,8 @@ module Quizzes
|
|||
:hide_correct_answers_at, :all_dates, :can_unpublish, :can_update,
|
||||
:require_lockdown_browser, :require_lockdown_browser_for_results,
|
||||
:require_lockdown_browser_monitor, :lockdown_browser_monitor_data,
|
||||
:speed_grader_url, :permissions, :quiz_reports_url, :quiz_statistics_url
|
||||
:speed_grader_url, :permissions, :quiz_reports_url, :quiz_statistics_url,
|
||||
:message_students_url
|
||||
|
||||
def_delegators :@controller,
|
||||
:api_v1_course_assignment_group_url,
|
||||
|
@ -23,29 +24,58 @@ module Quizzes
|
|||
:api_v1_course_quiz_submission_url,
|
||||
:api_v1_course_quiz_submissions_url,
|
||||
:api_v1_course_quiz_reports_url,
|
||||
:api_v1_course_quiz_statistics_url
|
||||
:api_v1_course_quiz_statistics_url,
|
||||
:api_v1_course_quiz_submission_users_url,
|
||||
:api_v1_course_quiz_submission_users_message_url
|
||||
|
||||
def_delegators :@object,
|
||||
:context,
|
||||
:submitted_students_visible_to,
|
||||
:unsubmitted_students_visible_to
|
||||
|
||||
has_one :assignment_group, embed: :ids, root: :assignment_group
|
||||
has_many :quiz_submissions, embed: :ids, root: :quiz_submissions
|
||||
has_many :submitted_students, embed: :ids, root: :submitted_students
|
||||
has_many :unsubmitted_students, embed: :ids, root: :unsubmitted_students
|
||||
|
||||
def submitted_students
|
||||
user_finder.submitted_students
|
||||
end
|
||||
|
||||
def unsubmitted_students
|
||||
user_finder.unsubmitted_students
|
||||
end
|
||||
|
||||
def message_students_url
|
||||
api_v1_course_quiz_submission_users_message_url(context, quiz)
|
||||
end
|
||||
|
||||
def speed_grader_url
|
||||
return nil unless show_speedgrader?
|
||||
speed_grader_course_gradebook_url(quiz.context, assignment_id: quiz.assignment.id)
|
||||
speed_grader_course_gradebook_url(context, assignment_id: quiz.assignment.id)
|
||||
end
|
||||
|
||||
def quiz_submissions_url
|
||||
if user_may_grade?
|
||||
api_v1_course_quiz_submissions_url(quiz.context, quiz)
|
||||
api_v1_course_quiz_submissions_url(context, quiz)
|
||||
else
|
||||
quiz_submission = quiz.quiz_submissions.where(user_id: current_user).first
|
||||
if quiz_submission
|
||||
api_v1_course_quiz_submission_url(quiz.context, quiz, quiz_submission)
|
||||
api_v1_course_quiz_submission_url(context, quiz, quiz_submission)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsubmitted_students_url
|
||||
api_v1_course_quiz_submission_users_url(context, quiz, submitted: 'false')
|
||||
end
|
||||
|
||||
def submitted_students_url
|
||||
api_v1_course_quiz_submission_users_url(context, quiz, submitted: true)
|
||||
end
|
||||
|
||||
def html_url
|
||||
controller.send(:course_quiz_url, context, quiz)
|
||||
end
|
||||
|
@ -73,8 +103,9 @@ module Quizzes
|
|||
super(keys).select do |key|
|
||||
case key
|
||||
when :all_dates then include_all_dates?
|
||||
when :access_code, :speed_grader_url then user_may_grade?
|
||||
when :access_code, :speed_grader_url, :message_students_url then user_may_grade?
|
||||
when :unpublishable then include_unpublishable?
|
||||
when :submitted_students, :unsubmitted_students then user_may_grade?
|
||||
else true
|
||||
end
|
||||
end
|
||||
|
@ -91,19 +122,18 @@ module Quizzes
|
|||
def question_count
|
||||
quiz.available_question_count
|
||||
end
|
||||
|
||||
def require_lockdown_browser
|
||||
quiz.require_lockdown_browser?
|
||||
end
|
||||
|
||||
|
||||
def require_lockdown_browser_for_results
|
||||
quiz.require_lockdown_browser_for_results?
|
||||
end
|
||||
|
||||
|
||||
def require_lockdown_browser_monitor
|
||||
quiz.require_lockdown_browser_monitor?
|
||||
end
|
||||
|
||||
|
||||
def lockdown_browser_monitor_data
|
||||
quiz.lockdown_browser_monitor_data
|
||||
end
|
||||
|
@ -126,7 +156,7 @@ module Quizzes
|
|||
end
|
||||
|
||||
def assignment_group_url
|
||||
api_v1_course_assignment_group_url(quiz.context, quiz.assignment_group.id)
|
||||
api_v1_course_assignment_group_url(context, quiz.assignment_group.id)
|
||||
end
|
||||
|
||||
def quiz_reports_url
|
||||
|
@ -144,7 +174,7 @@ module Quizzes
|
|||
private
|
||||
|
||||
def show_speedgrader?
|
||||
quiz.assignment.present? && quiz.published? && quiz.context.allows_speed_grader?
|
||||
quiz.assignment.present? && quiz.published? && context.allows_speed_grader?
|
||||
end
|
||||
|
||||
def due_dates
|
||||
|
@ -176,5 +206,8 @@ module Quizzes
|
|||
quiz.grants_right?(current_user, session, :grade)
|
||||
end
|
||||
|
||||
def user_finder
|
||||
@user_finder ||= Quizzes::QuizUserFinder.new(quiz, current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1330,6 +1330,11 @@ routes.draw do
|
|||
post "courses/:course_id/quizzes/:id/reorder", :action => :reorder, :path_name => 'course_quiz_reorder'
|
||||
end
|
||||
|
||||
scope(:controller => 'quizzes/quiz_submission_users') do
|
||||
get "courses/:course_id/quizzes/:id/submission_users", :action => :index, :path_name => 'course_quiz_submission_users'
|
||||
post "courses/:course_id/quizzes/:id/submission_users/message", :action => :message, :path_name => 'course_quiz_submission_users_message'
|
||||
end
|
||||
|
||||
scope(:controller => 'quizzes/quiz_groups') do
|
||||
post "courses/:course_id/quizzes/:quiz_id/groups", :action => :create, :path_name => 'course_quiz_group_create'
|
||||
put "courses/:course_id/quizzes/:quiz_id/groups/:id", :action => :update, :path_name => 'course_quiz_group_update'
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
# Copyright (C) 2014 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/>.
|
||||
#
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../api_spec_helper')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../../../models/quizzes/quiz_user_messager_spec_helper')
|
||||
|
||||
describe Quizzes::QuizSubmissionUsersController, type: :request do
|
||||
before do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
course_quiz(true)
|
||||
end
|
||||
|
||||
def controller_options(options)
|
||||
options.reverse_merge!({
|
||||
controller: "quizzes/quiz_submission_users",
|
||||
action: "message",
|
||||
format: "json",
|
||||
course_id: @course.id,
|
||||
id: @quiz.id
|
||||
})
|
||||
end
|
||||
|
||||
describe "POST message" do
|
||||
include Quizzes::QuizUserMessagerSpecHelper
|
||||
|
||||
before do
|
||||
@finder = Quizzes::QuizUserFinder.new(@quiz, @teacher)
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@user = @teacher
|
||||
end
|
||||
|
||||
def send_message(target_group)
|
||||
raw_api_call(
|
||||
:post,
|
||||
"/api/v1/courses/#{@course.id}/quizzes/#{@quiz.id}/submission_users/message",
|
||||
controller_options(
|
||||
action: 'message',
|
||||
conversations: [
|
||||
{ body: 'Ohi!', recipients: target_group.to_s }
|
||||
]
|
||||
)
|
||||
)
|
||||
run_jobs
|
||||
end
|
||||
|
||||
it "sends a message to unsubmitted users" do
|
||||
expect { send_message(:unsubmitted) }.to change { recipient_messages(:unsubmitted) }.by 1
|
||||
recipient_messages(:submitted).should == 0
|
||||
end
|
||||
|
||||
it "sends a message to submitted users" do
|
||||
sub = @quiz.generate_submission(@student)
|
||||
sub.mark_completed
|
||||
sub.grade_submission
|
||||
expect { send_message(:submitted) }.to change { recipient_messages(:submitted) }.by 1
|
||||
recipient_messages(:unsubmitted).should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET submission_users" do
|
||||
|
||||
def get_submitted_users(options={})
|
||||
options = controller_options(options.reverse_merge!(action: 'index'))
|
||||
raw_api_call(
|
||||
:get,
|
||||
"/api/v1/courses/#{@course.id}/quizzes/#{@quiz.id}/submission_users",
|
||||
options,
|
||||
{ 'Accept' => 'application/json'}
|
||||
)
|
||||
JSON.parse(response.body) if response.success?
|
||||
end
|
||||
|
||||
it "does not allow students to view information at the endpoint" do
|
||||
course_with_student_logged_in(course: @course, active_all: true)
|
||||
get_submitted_users
|
||||
response.should_not be_success
|
||||
end
|
||||
|
||||
it "allows teachers to see submitted students with ?submitted=true" do
|
||||
course_with_student(active_all: true, course: @course)
|
||||
quiz_with_graded_submission([], course: @course, user: @student)
|
||||
@user = @teacher
|
||||
json = get_submitted_users(submitted: true)
|
||||
response.should be_success
|
||||
json['users'].first['id'].should == @student.id
|
||||
end
|
||||
|
||||
it "allows teachers to see unsubmitted students with ?submitted=false" do
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@student_frd = @student
|
||||
quiz_with_graded_submission([], course: @course, user: @student_frd)
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@user = @teacher
|
||||
json = get_submitted_users(submitted: false)
|
||||
response.should be_success
|
||||
user_ids = json['users'].map { |h| h['id'] }
|
||||
user_ids.should_not include @student_frd.id
|
||||
user_ids.should include @student.id
|
||||
end
|
||||
|
||||
it "allows teachers to see all students for quiz when submitted parameter not passed" do
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@student_frd = @student
|
||||
quiz_with_graded_submission([], course: @course, user: @student_frd)
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@user = @teacher
|
||||
json = get_submitted_users
|
||||
response.should be_success
|
||||
user_ids = json['users'].map { |h| h['id'] }
|
||||
user_ids.should include @student_frd.id
|
||||
user_ids.should include @student.id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
#
|
||||
# Copyright (C) 2014 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/>.
|
||||
#
|
||||
require 'spec_helper'
|
||||
|
||||
describe Quizzes::QuizUserFinder do
|
||||
|
||||
before do
|
||||
course_with_teacher_logged_in(course: @course, active_all: true)
|
||||
course_quiz(true)
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@submitted_student = @student
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@unsubmitted_student = @student
|
||||
sub = @quiz.generate_submission(@submitted_student)
|
||||
sub.mark_completed
|
||||
sub.grade_submission
|
||||
@finder = Quizzes::QuizUserFinder.new(@quiz, @teacher)
|
||||
end
|
||||
|
||||
def students
|
||||
[ @unsubmitted_student, @submitted_student ]
|
||||
end
|
||||
|
||||
it "(#all_students) finds all students" do
|
||||
@finder.all_students.should =~ students
|
||||
end
|
||||
|
||||
it "(#unsubmitted_students) finds unsubmitted students" do
|
||||
@finder.unsubmitted_students.should == [ @unsubmitted_student ]
|
||||
end
|
||||
|
||||
it "(#submitted_student) finds submitted students" do
|
||||
@finder.submitted_students.should == [ @submitted_student ]
|
||||
end
|
||||
|
||||
it "doesn't find submissions from teachers for preview submissions" do
|
||||
sub = @quiz.generate_submission(@teacher, preview=true)
|
||||
sub.grade_submission
|
||||
sub.save!
|
||||
@finder.submitted_students.should_not include @teacher
|
||||
@finder.unsubmitted_students.should_not include @teacher
|
||||
@finder.unsubmitted_students.should_not be_empty
|
||||
@finder.all_students.should_not include @teacher
|
||||
end
|
||||
|
||||
it "doesn't duplicate the same user found in multiple sections" do
|
||||
add_section('The Mother We Share')
|
||||
student_in_section(@course_section, user: @submitted_student)
|
||||
@finder.all_students.should =~ students
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
# Copyright (C) 2014 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/>.
|
||||
#
|
||||
|
||||
require 'spec_helper'
|
||||
require File.expand_path(File.dirname(__FILE__) + "/quiz_user_messager_spec_helper.rb")
|
||||
|
||||
describe Quizzes::QuizUserMessager do
|
||||
include Quizzes::QuizUserMessagerSpecHelper
|
||||
|
||||
before do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
course_quiz(true)
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@unsubmitted = @student
|
||||
course_with_student(active_all: true, course: @course)
|
||||
@submitted = @student
|
||||
submission = @quiz.generate_submission(@submitted)
|
||||
submission.mark_completed
|
||||
submission.grade_submission
|
||||
@finder = Quizzes::QuizUserFinder.new(@quiz, @teacher)
|
||||
end
|
||||
|
||||
describe "#send" do
|
||||
|
||||
it "sends to all students" do
|
||||
expect { send_message }.to change { recipient_messages('all') }.by 2
|
||||
end
|
||||
|
||||
it "can send to either submitted or unsubmitted students" do
|
||||
expect {
|
||||
send_message('submitted')
|
||||
}.to change { recipient_messages('submitted') }.by 1
|
||||
|
||||
expect {
|
||||
send_message('unsubmitted')
|
||||
}.to change { recipient_messages('unsubmitted') }.by 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright (C) 2014 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 Quizzes
|
||||
module QuizUserMessagerSpecHelper
|
||||
|
||||
def conversation(recipients)
|
||||
{
|
||||
subject: "Do you want ants?",
|
||||
body: "Because that's how you get ants",
|
||||
recipients: recipients
|
||||
}
|
||||
end
|
||||
|
||||
def send_message(recipients='all')
|
||||
options = {
|
||||
quiz: @quiz,
|
||||
sender: @teacher,
|
||||
conversation: conversation(recipients),
|
||||
root_account_id: Account.default.id
|
||||
}
|
||||
Quizzes::QuizUserMessager.new(options).send
|
||||
run_jobs
|
||||
end
|
||||
|
||||
def recipient_messages(target_group)
|
||||
recipients = @finder.send("#{target_group}_students")
|
||||
recipients.map(&:all_conversations).map(&:size).reduce(:+) || 0
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,6 +1,15 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Quizzes::QuizSerializer do
|
||||
|
||||
def quiz_serializer(options={})
|
||||
options.reverse_merge!({
|
||||
controller: controller,
|
||||
scope: @user,
|
||||
session: @session
|
||||
})
|
||||
Quizzes::QuizSerializer.new(@quiz, options)
|
||||
end
|
||||
let(:quiz) { @quiz }
|
||||
let(:context ) { @context }
|
||||
let(:serializer) { @serializer }
|
||||
|
@ -24,10 +33,7 @@ describe Quizzes::QuizSerializer do
|
|||
controller.stubs(:session).returns session
|
||||
controller.stubs(:context).returns context
|
||||
@quiz.stubs(:grants_right?).at_least_once.returns true
|
||||
@serializer = Quizzes::QuizSerializer.new(@quiz,
|
||||
controller: controller,
|
||||
scope: @user,
|
||||
session: @session)
|
||||
@serializer = quiz_serializer
|
||||
@json = @serializer.as_json[:quiz]
|
||||
end
|
||||
|
||||
|
@ -95,6 +101,15 @@ describe Quizzes::QuizSerializer do
|
|||
serializer.as_json[:quiz][:question_count].should == 5
|
||||
end
|
||||
|
||||
it "sends the message_students_url when user can grade" do
|
||||
quiz.expects(:grants_right?).at_least_once.returns true
|
||||
serializer.as_json[:quiz][:message_students_url].should ==
|
||||
controller.send(:api_v1_course_quiz_submission_users_message_url, quiz, quiz.context)
|
||||
|
||||
quiz.expects(:grants_right?).at_least_once.returns false
|
||||
serializer.as_json[:quiz].should_not have_key :message_students_url
|
||||
end
|
||||
|
||||
describe "id" do
|
||||
|
||||
it "stringifys when stringify_json_ids? is true" do
|
||||
|
@ -116,8 +131,7 @@ describe Quizzes::QuizSerializer do
|
|||
quiz.expects(:locked_for?).
|
||||
with(@user, check_policies: true, context: @context).
|
||||
returns({due_at: true})
|
||||
json = Quizzes::QuizSerializer.new(quiz, scope: @user, controller: controller).
|
||||
as_json[:quiz]
|
||||
json = quiz_serializer.as_json[:quiz]
|
||||
json.should have_key :lock_info
|
||||
json.should have_key :lock_explanation
|
||||
json[:locked_for_user].should == true
|
||||
|
@ -125,8 +139,7 @@ describe Quizzes::QuizSerializer do
|
|||
quiz.expects(:locked_for?).
|
||||
with(@user, check_policies: true, context: @context).
|
||||
returns false
|
||||
json = Quizzes::QuizSerializer.new(quiz, scope: @user, controller: controller).
|
||||
as_json[:quiz]
|
||||
json = quiz_serializer.as_json[:quiz]
|
||||
json.should_not have_key :lock_info
|
||||
json.should_not have_key :lock_explanation
|
||||
json[:locked_for_user].should == false
|
||||
|
@ -184,9 +197,7 @@ describe Quizzes::QuizSerializer do
|
|||
it "sends the url for all submissions when user may grade" do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
quiz_with_graded_submission([], course: @course)
|
||||
serializer = Quizzes::QuizSerializer.new(@quiz,
|
||||
controller: controller,
|
||||
scope: @teacher)
|
||||
serializer = quiz_serializer(scope: @teacher)
|
||||
serializer.as_json[:quiz]['links']['quiz_submissions'].should ==
|
||||
controller.send(:api_v1_course_quiz_submissions_url, @quiz.context.id, @quiz.id)
|
||||
end
|
||||
|
@ -194,22 +205,17 @@ describe Quizzes::QuizSerializer do
|
|||
it "sends the url to a student's submission for students" do
|
||||
course_with_student_logged_in(active_all: true)
|
||||
quiz_with_graded_submission([], user: @student, course: @course)
|
||||
serializer = Quizzes::QuizSerializer.new(@quiz,
|
||||
controller: controller,
|
||||
scope: @student)
|
||||
serializer = quiz_serializer(scope: @student)
|
||||
serializer.as_json[:quiz]['links']['quiz_submissions'].should ==
|
||||
controller.send(:api_v1_course_quiz_submission_url,
|
||||
@quiz.context.id,
|
||||
@quiz.id,
|
||||
@quiz.quiz_submissions.where(user_id: @student).first.id)
|
||||
|
||||
end
|
||||
|
||||
it "sends nil if user can't grade and doesn't have a submission" do
|
||||
course_with_student_logged_in(active_all: true)
|
||||
serializer = Quizzes::QuizSerializer.new(@quiz,
|
||||
controller: controller,
|
||||
scope: @student)
|
||||
serializer = quiz_serializer(scope: @student)
|
||||
serializer.as_json[:quiz]['links']['quiz_submissions'].should be_nil
|
||||
end
|
||||
end
|
||||
|
@ -243,6 +249,51 @@ describe Quizzes::QuizSerializer do
|
|||
controller.send(:api_v1_course_quiz_statistics_url, 3, quiz.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe "submitted_students" do
|
||||
|
||||
it "sends nil if user can't grade" do
|
||||
course_with_student_logged_in(active_all: true)
|
||||
@quiz.unstub(:grants_right?)
|
||||
serializer = quiz_serializer(scope: @student)
|
||||
serializer.as_json[:quiz]['links'].should_not have_key 'unsubmitted_students'
|
||||
end
|
||||
|
||||
it "sends a url if there are submissions and user can grade" do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
course_with_student_logged_in(active_all: true, course: @course)
|
||||
quiz_with_graded_submission([], user: @student, course: @course)
|
||||
serializer = quiz_serializer(scope: @teacher)
|
||||
serializer.as_json[:quiz]['links']['submitted_students'].
|
||||
should == controller.send(:api_v1_course_quiz_submission_users_url,
|
||||
@quiz.context,
|
||||
@quiz,
|
||||
submitted: true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "unsubmitted_students" do
|
||||
|
||||
it "sends nil if user can't grade" do
|
||||
@quiz.unstub(:grants_right?)
|
||||
course_with_student_logged_in(active_all: true)
|
||||
serializer = quiz_serializer(scope: @student)
|
||||
serializer.as_json[:quiz]['links'].should_not have_key 'unsubmitted_students'
|
||||
end
|
||||
|
||||
it "sends a url if there are submissions and user can grade" do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
course_with_student_logged_in(active_all: true, course: @course)
|
||||
course_with_student_logged_in(active_all: true, course: @course)
|
||||
quiz_with_graded_submission([], user: @student, course: @course)
|
||||
serializer = quiz_serializer(scope: @teacher)
|
||||
serializer.as_json[:quiz]['links']['unsubmitted_students'].
|
||||
should == controller.send(:api_v1_course_quiz_submission_users_url,
|
||||
@quiz.context,
|
||||
@quiz,
|
||||
submitted: 'false')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "permissions" do
|
||||
|
|
|
@ -638,12 +638,12 @@ end
|
|||
end
|
||||
|
||||
def student_in_section(section, opts={})
|
||||
user
|
||||
enrollment = section.course.enroll_user(@user, 'StudentEnrollment', :section => section)
|
||||
@user.save!
|
||||
student = opts.fetch(:user) { user }
|
||||
enrollment = section.course.enroll_user(student, 'StudentEnrollment', :section => section)
|
||||
student.save!
|
||||
enrollment.workflow_state = 'active'
|
||||
enrollment.save!
|
||||
@user
|
||||
student
|
||||
end
|
||||
|
||||
def teacher_in_course(opts={})
|
||||
|
|
Loading…
Reference in New Issue