make needs-grading counts respect sections; fixes #11001

test plan:
 * Create a course with multiple sections
 * Enroll students in both sections, and submit assignments. Keep
   track of how many submissions were made in each section.
 * Create a TA in one section, with limited privileges, and log in
   as that TA.
 * Check the to-do list on the right panel, and make sure it only
   includes submissions from the TA's section.
 * Check the TODO items API, and make sure the needs_grading_count
   figure only includes submissions from the TA's section.
 * Check the needs_grading_count in the Assignments API;
   it should include only submissions in the TA's section
 * Check the needs_grading_count in the Courses API;
   it should sum all the submissions to all the assignments
   in the TA's section of the course

Change-Id: I5f1f47321bb909abc24deabdfa47b8301dc83d8f
Reviewed-on: https://gerrit.instructure.com/14026
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Zach Pendleton <zachp@instructure.com>
This commit is contained in:
Jeremy Stanley 2012-09-28 11:34:02 -06:00
parent 67aee3011b
commit f8012a8b6b
12 changed files with 239 additions and 47 deletions

View File

@ -526,7 +526,7 @@ class ApplicationController < ActionController::Base
@ungraded_assignments = @assignments.select{|a| @ungraded_assignments = @assignments.select{|a|
a.grants_right?(@current_user, session, :grade) && a.grants_right?(@current_user, session, :grade) &&
a.expects_submission? && a.expects_submission? &&
a.needs_grading_count > 0 a.needs_grading_count_for_user(@current_user) > 0
} }
@assignment_groups = @groups @assignment_groups = @groups
@past_assignments = @assignments.select{ |a| a.due_at && a.due_at < Time.now } @past_assignments = @assignments.select{ |a| a.due_at && a.due_at < Time.now }

View File

@ -1763,4 +1763,31 @@ class Assignment < ActiveRecord::Base
end end
end end
def needs_grading_count_for_user(user)
vis = self.context.section_visibilities_for(user)
# the needs_grading_count trigger should change self.updated_at, invalidating the cache
Rails.cache.fetch(['assignment_user_grading_count', self, user].cache_key) do
case self.context.enrollment_visibility_level_for(user, vis)
when :full
self.needs_grading_count
when :sections
self.submissions.count('DISTINCT submissions.id',
:joins => "INNER JOIN enrollments e ON e.user_id = submissions.user_id",
:conditions => [<<-SQL, self.id, self.context.id, vis.map {|v| v[:course_section_id]}])
submissions.assignment_id = ?
AND e.course_id = ?
AND e.course_section_id in (?)
AND e.type IN ('StudentEnrollment', 'StudentViewEnrollment')
AND e.workflow_state = 'active'
AND submissions.submission_type IS NOT NULL
AND (submissions.workflow_state = 'pending_review'
OR (submissions.workflow_state = 'submitted'
AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission)))
SQL
else
0
end
end
end
end end

View File

@ -57,33 +57,36 @@ class Enrollment < ActiveRecord::Base
def self.needs_grading_trigger_sql def self.needs_grading_trigger_sql
no_other_enrollments_sql = "NOT " + active_student_subselect("user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id") no_other_enrollments_sql = "NOT " + active_student_subselect("user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id")
default_sql = <<-SQL
# IN (...) subselects perform poorly in mysql, plus we want to avoid UPDATE assignments SET needs_grading_count = needs_grading_count + %s, updated_at = {{now}}
# locking rows in other tables
{:default => <<-SQL, :mysql => <<-MYSQL}
UPDATE assignments SET needs_grading_count = needs_grading_count + %s
WHERE context_id = NEW.course_id WHERE context_id = NEW.course_id
AND context_type = 'Course' AND context_type = 'Course'
AND EXISTS ( AND EXISTS (
SELECT 1 SELECT 1
FROM submissions FROM submissions
WHERE user_id = NEW.user_id WHERE user_id = NEW.user_id
AND assignment_id = assignments.id AND assignment_id = assignments.id
AND (#{Submission.needs_grading_conditions}) AND (#{Submission.needs_grading_conditions})
LIMIT 1 LIMIT 1
) )
AND #{no_other_enrollments_sql}; AND #{no_other_enrollments_sql};
SQL SQL
IF #{no_other_enrollments_sql} THEN # IN (...) subselects perform poorly in mysql, plus we want to avoid locking rows in other tables
UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + %s # also, every database uses a different construct for a current UTC timestamp
WHERE context_id = NEW.course_id { :default => default_sql.gsub("{{now}}", "now()"),
AND context_type = 'Course' :postgresql => default_sql.gsub("{{now}}", "now() AT TIME ZONE 'UTC'"),
AND assignments.id = submissions.assignment_id :sqlite => default_sql.gsub("{{now}}", "datetime('now')"),
AND submissions.user_id = NEW.user_id :mysql => <<-MYSQL }
AND (#{Submission.needs_grading_conditions}); IF #{no_other_enrollments_sql} THEN
END IF; UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + %s, updated_at = utc_timestamp()
MYSQL WHERE context_id = NEW.course_id
AND context_type = 'Course'
AND assignments.id = submissions.assignment_id
AND submissions.user_id = NEW.user_id
AND (#{Submission.needs_grading_conditions});
END IF;
MYSQL
end end
trigger.after(:insert).where(active_student_conditions('NEW')) do trigger.after(:insert).where(active_student_conditions('NEW')) do

View File

@ -111,24 +111,30 @@ class Submission < ActiveRecord::Base
after_save :update_quiz_submission after_save :update_quiz_submission
def self.needs_grading_trigger_sql def self.needs_grading_trigger_sql
<<-SQL # every database uses a different construct for a current UTC timestamp...
default_sql = <<-SQL
UPDATE assignments UPDATE assignments
SET needs_grading_count = needs_grading_count + %s SET needs_grading_count = needs_grading_count + %s, updated_at = {{now}}
WHERE id = NEW.assignment_id WHERE id = NEW.assignment_id
AND context_type = 'Course' AND context_type = 'Course'
AND #{Enrollment.active_student_subselect("user_id = NEW.user_id AND course_id = assignments.context_id")}; AND #{Enrollment.active_student_subselect("user_id = NEW.user_id AND course_id = assignments.context_id")};
SQL SQL
{ :default => default_sql.gsub("{{now}}", "now()"),
:postgresql => default_sql.gsub("{{now}}", "now() AT TIME ZONE 'UTC'"),
:sqlite => default_sql.gsub("{{now}}", "datetime('now')"),
:mysql => default_sql.gsub("{{now}}", "utc_timestamp()") }
end end
trigger.after(:insert) do |t| trigger.after(:insert) do |t|
t.where("#{needs_grading_conditions("NEW")}") do t.where("#{needs_grading_conditions("NEW")}") do
needs_grading_trigger_sql % 1 Hash[needs_grading_trigger_sql.map{|key, value| [key, value % 1]}]
end end
end end
trigger.after(:update) do |t| trigger.after(:update) do |t|
t.where("(#{needs_grading_conditions("NEW")}) <> (#{needs_grading_conditions("OLD")})") do t.where("(#{needs_grading_conditions("NEW")}) <> (#{needs_grading_conditions("OLD")})") do
needs_grading_trigger_sql % "CASE WHEN (#{needs_grading_conditions('NEW')}) THEN 1 ELSE -1 END" Hash[needs_grading_trigger_sql.map{|key, value| [key, value % "CASE WHEN (#{needs_grading_conditions('NEW')}) THEN 1 ELSE -1 END"]}]
end end
end end

View File

@ -1465,16 +1465,8 @@ class User < ActiveRecord::Base
def assignments_needing_grading(opts={}) def assignments_needing_grading(opts={})
course_codes = opts[:contexts] ? (Array(opts[:contexts]).map(&:asset_string) & current_admin_enrollment_course_codes) : current_admin_enrollment_course_codes course_codes = opts[:contexts] ? (Array(opts[:contexts]).map(&:asset_string) & current_admin_enrollment_course_codes) : current_admin_enrollment_course_codes
ignored_ids = ignored_items(:grading).select{|key, val| key.match(/\Aassignment_/) }.map{|key, val| key.sub(/\Aassignment_/, "") } ignored_ids = ignored_items(:grading).select{|key, val| key.match(/\Aassignment_/) }.map{|key, val| key.sub(/\Aassignment_/, "") }
Assignment.for_context_codes(course_codes).active.expecting_submission.need_grading_info(opts[:limit] || 15, ignored_ids) Assignment.for_context_codes(course_codes).active.expecting_submission.need_grading_info(opts[:limit] || 15, ignored_ids).reject{|a| a.needs_grading_count_for_user(self) == 0}
end end
memoize :assignments_needing_grading
def assignments_needing_grading_total_count(opts={})
course_codes = opts[:contexts] ? (Array(opts[:contexts]).map(&:asset_string) & current_admin_enrollment_course_codes) : current_admin_enrollment_course_codes
ignored_ids = ignored_items(:grading).select{|key, val| key.match(/\Aassignment_/) }.map{|key, val| key.sub(/\Aassignment_/, "") }
Assignment.for_context_codes(course_codes).active.expecting_submission.need_grading_info(nil, ignored_ids).size
end
memoize :assignments_needing_grading_total_count
def generate_access_verifier(ts) def generate_access_verifier(ts)
require 'openssl' require 'openssl'

View File

@ -44,7 +44,7 @@ cache(safe_cache_key([@current_user, contexts, 'a_need_grading']), cache_opts) d
</span> </span>
<% end %> <% end %>
<b><%= t 'headings.grade', 'Grade %{assignment}', :assignment => assignment.title %></b> <b><%= t 'headings.grade', 'Grade %{assignment}', :assignment => assignment.title %></b>
<em><%= t 'need_grading_count', { :one => '1 needs grading', :other => '%{count} need grading' }, :count => assignment.needs_grading_count %></em> <em><%= t 'need_grading_count', { :one => '1 needs grading', :other => '%{count} need grading' }, :count => assignment.needs_grading_count_for_user(@current_user) %></em>
</a> </a>
<a class='disable_item_link grading' title="<%= t('links.title.ignore', %{Ignore this assignment}) %>" href="#" data-api-href="<%= api_v1_users_todo_ignore_url(assignment.asset_string, 'grading') %>"><%= image_tag "earmark_hover.png", :alt => t('images.alt.ignore', 'ignore') %></a> <a class='disable_item_link grading' title="<%= t('links.title.ignore', %{Ignore this assignment}) %>" href="#" data-api-href="<%= api_v1_users_todo_ignore_url(assignment.asset_string, 'grading') %>"><%= image_tag "earmark_hover.png", :alt => t('images.alt.ignore', 'ignore') %></a>
</li> </li>

View File

@ -10,7 +10,7 @@
<% if key == :needing_submitting %> <% if key == :needing_submitting %>
<%= before_label :due, "due" %> <%= datetime_string(menu_assignment.due_at, :due_date, nil, true) %> <%= before_label :due, "due" %> <%= datetime_string(menu_assignment.due_at, :due_date, nil, true) %>
<% elsif key == :needing_grading %> <% elsif key == :needing_grading %>
<%= t :needs_grading_count, { :one => "1 needs grading", :other => "%{count} need grading"}, :count => menu_assignment.needs_grading_count %> <%= t :needs_grading_count, { :one => "1 needs grading", :other => "%{count} need grading"}, :count => menu_assignment.needs_grading_count_for_user(@current_user) %>
<% elsif key == :recently_graded %> <% elsif key == :recently_graded %>
<% if menu_assignment.grading_type == 'points' %> <% if menu_assignment.grading_type == 'points' %>
<%= "#{menu_assignment.score}/#{menu_assignment.points_possible}" %> <%= "#{menu_assignment.score}/#{menu_assignment.points_possible}" %>

View File

@ -0,0 +1,94 @@
# This migration was auto-generated via `rake db:generate_trigger_migration'.
# While you can edit this file, any changes you make to the definitions here
# will be undone by the next auto-generated trigger migration.
class CreateTriggersEnrollmentsInsertAndEnrollmentsUpdateAndSubmissionsInsertAndSubmissionsUpdate1 < ActiveRecord::Migration
tag :predeploy
def self.up
drop_trigger("enrollments_after_insert_row_when_new_type_in_studentenrollm_tr", "enrollments", :generated => true)
drop_trigger("enrollments_after_update_row_when_new_type_in_studentenrollm_tr", "enrollments", :generated => true)
drop_trigger("submissions_after_insert_row_tr", "submissions", :generated => true)
drop_trigger("submissions_after_insert_row_when_new_submission_type_is_not_tr", "submissions", :generated => true)
drop_trigger("submissions_after_update_row_tr", "submissions", :generated => true)
drop_trigger("submissions_after_update_row_when_new_submission_type_is_not_tr", "submissions", :generated => true)
create_trigger("enrollments_after_insert_row_when_new_type_in_studentenrollm_tr", :generated => true, :compatibility => 1).
on("enrollments").
after(:insert).
where("(NEW.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND NEW.workflow_state = 'active')") do
{:default=>" UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = now()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>" UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = datetime('now')\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = now() AT TIME ZONE 'UTC'\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>" IF NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1) THEN\n UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + 1, updated_at = utc_timestamp()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND assignments.id = submissions.assignment_id\n AND submissions.user_id = NEW.user_id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) );\n END IF;"}
end
create_trigger("enrollments_after_update_row_when_new_type_in_studentenrollm_tr", :generated => true, :compatibility => 1).
on("enrollments").
after(:update).
where("(NEW.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND NEW.workflow_state = 'active') <> (OLD.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND OLD.workflow_state = 'active')") do
{:default=>" UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = now()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>" UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = datetime('now')\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = now() AT TIME ZONE 'UTC'\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>" IF NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1) THEN\n UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = utc_timestamp()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND assignments.id = submissions.assignment_id\n AND submissions.user_id = NEW.user_id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) );\n END IF;"}
end
create_trigger("submissions_after_insert_row_tr", :generated => true, :compatibility => 1).
on("submissions").
after(:insert) do |t|
t.where(" NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ") do
{:default=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = now()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = datetime('now')\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = now() AT TIME ZONE 'UTC'\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = utc_timestamp()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);"}
end
end
create_trigger("submissions_after_update_row_tr", :generated => true, :compatibility => 1).
on("submissions").
after(:update) do |t|
t.where("( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) <> ( OLD.submission_type IS NOT NULL AND (OLD.workflow_state = 'pending_review' OR (OLD.workflow_state = 'submitted' AND (OLD.score IS NULL OR NOT OLD.grade_matches_current_submission) ) ) )") do
{:default=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = now()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = datetime('now')\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = now() AT TIME ZONE 'UTC'\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = utc_timestamp()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);"}
end
end
end
def self.down
drop_trigger("enrollments_after_insert_row_when_new_type_in_studentenrollm_tr", "enrollments", :generated => true)
drop_trigger("enrollments_after_update_row_when_new_type_in_studentenrollm_tr", "enrollments", :generated => true)
drop_trigger("submissions_after_insert_row_tr", "submissions", :generated => true)
drop_trigger("submissions_after_insert_row_when_new_submission_type_is_not_tr", "submissions", :generated => true)
drop_trigger("submissions_after_update_row_tr", "submissions", :generated => true)
drop_trigger("submissions_after_update_row_when_new_submission_type_is_not_tr", "submissions", :generated => true)
create_trigger("enrollments_after_insert_row_when_new_type_in_studentenrollm_tr", :generated => true, :compatibility => 1).
on("enrollments").
after(:insert).
where("(NEW.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND NEW.workflow_state = 'active')") do
{:default=>"\n UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = now()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>"\n UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = datetime('now')\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments SET needs_grading_count = needs_grading_count + 1, updated_at = now() AT TIME ZONE 'UTC'\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>"\n IF NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1) THEN\n UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + 1, updated_at = utc_timestamp()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND assignments.id = submissions.assignment_id\n AND submissions.user_id = NEW.user_id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) );\n END IF;"}
end
create_trigger("enrollments_after_update_row_when_new_type_in_studentenrollm_tr", :generated => true, :compatibility => 1).
on("enrollments").
after(:update).
where("(NEW.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND NEW.workflow_state = 'active') <> (OLD.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND OLD.workflow_state = 'active')") do
{:default=>"\n UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = now()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>"\n UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = datetime('now')\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = now() AT TIME ZONE 'UTC'\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND EXISTS (\n SELECT 1\n FROM submissions\n WHERE user_id = NEW.user_id\n AND assignment_id = assignments.id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) )\n LIMIT 1\n )\n AND NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>"\n IF NOT EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = NEW.course_id AND id <> NEW.id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1) THEN\n UPDATE assignments, submissions SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END, updated_at = utc_timestamp()\n WHERE context_id = NEW.course_id\n AND context_type = 'Course'\n AND assignments.id = submissions.assignment_id\n AND submissions.user_id = NEW.user_id\n AND ( submissions.submission_type IS NOT NULL AND (submissions.workflow_state = 'pending_review' OR (submissions.workflow_state = 'submitted' AND (submissions.score IS NULL OR NOT submissions.grade_matches_current_submission) ) ) );\n END IF;"}
end
create_trigger("submissions_after_insert_row_tr", :generated => true, :compatibility => 1).
on("submissions").
after(:insert) do |t|
t.where(" NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ") do
{:default=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = now()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = datetime('now')\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = now() AT TIME ZONE 'UTC'\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + 1, updated_at = utc_timestamp()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);"}
end
end
create_trigger("submissions_after_update_row_tr", :generated => true, :compatibility => 1).
on("submissions").
after(:update) do |t|
t.where("( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) <> ( OLD.submission_type IS NOT NULL AND (OLD.workflow_state = 'pending_review' OR (OLD.workflow_state = 'submitted' AND (OLD.score IS NULL OR NOT OLD.grade_matches_current_submission) ) ) )") do
{:default=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = now()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :sqlite=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = datetime('now')\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :postgresql=>" UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = now() AT TIME ZONE 'UTC'\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);", :mysql=>"\n UPDATE assignments\n SET needs_grading_count = needs_grading_count + CASE WHEN ( NEW.submission_type IS NOT NULL AND (NEW.workflow_state = 'pending_review' OR (NEW.workflow_state = 'submitted' AND (NEW.score IS NULL OR NOT NEW.grade_matches_current_submission) ) ) ) THEN 1 ELSE -1 END, updated_at = utc_timestamp()\n WHERE id = NEW.assignment_id\n AND context_type = 'Course'\n AND EXISTS (SELECT 1 FROM enrollments WHERE user_id = NEW.user_id AND course_id = assignments.context_id AND (enrollments.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND enrollments.workflow_state = 'active') LIMIT 1);"}
end
end
end
end

View File

@ -33,7 +33,7 @@ module Api::V1::Assignment
end end
if assignment.grants_right?(user, :grade) if assignment.grants_right?(user, :grade)
hash['needs_grading_count'] = assignment.needs_grading_count hash['needs_grading_count'] = assignment.needs_grading_count_for_user(user)
end end
hash['submission_types'] = assignment.submission_types.split(',') hash['submission_types'] = assignment.submission_types.split(',')

View File

@ -46,7 +46,7 @@ module Api::V1::Course
end end
hash['calendar'] = { 'ics' => "#{feeds_calendar_url(course.feed_code)}.ics" } hash['calendar'] = { 'ics' => "#{feeds_calendar_url(course.feed_code)}.ics" }
if include_grading && enrollments && enrollments.any? { |e| e.participating_instructor? } if include_grading && enrollments && enrollments.any? { |e| e.participating_instructor? }
hash['needs_grading_count'] = course.assignments.active.sum('needs_grading_count') hash['needs_grading_count'] = course.assignments.active.to_a.sum{|a| a.needs_grading_count_for_user(user)}
end end
if include_syllabus if include_syllabus
hash['syllabus_body'] = course.syllabus_body hash['syllabus_body'] = course.syllabus_body

View File

@ -31,7 +31,7 @@ module Api::V1::TodoItem
"#{course_assignment_url(assignment.context_id, assignment.id)}#submit" "#{course_assignment_url(assignment.context_id, assignment.id)}#submit"
}).tap do |hash| }).tap do |hash|
if todo_type == 'grading' if todo_type == 'grading'
hash['needs_grading_count'] = assignment.needs_grading_count hash['needs_grading_count'] = assignment.needs_grading_count_for_user(user)
end end
end end
end end

View File

@ -167,6 +167,76 @@ describe Assignment do
@assignment.needs_grading_count.should eql(0) @assignment.needs_grading_count.should eql(0)
@user.enrollments.count(:conditions => "workflow_state = 'active'").should eql(1) @user.enrollments.count(:conditions => "workflow_state = 'active'").should eql(1)
end end
it "updated_at should be set when needs_grading_count changes due to a submission" do
setup_assignment_with_homework
@assignment.needs_grading_count.should eql(1)
old_timestamp = Time.now.utc - 1.minute
Assignment.update_all({:updated_at => old_timestamp}, {:id => @assignment.id})
@assignment.grade_student(@user, :grade => "0")
@assignment.reload
@assignment.needs_grading_count.should eql(0)
@assignment.updated_at.should > old_timestamp
end
it "updated_at should be set when needs_grading_count changes due to an enrollment change" do
setup_assignment_with_homework
old_timestamp = Time.now.utc - 1.minute
@assignment.needs_grading_count.should eql(1)
Assignment.update_all({:updated_at => old_timestamp}, {:id => @assignment.id})
@course.offer!
@course.enrollments.find_by_user_id(@user.id).destroy
@assignment.reload
@assignment.needs_grading_count.should eql(0)
@assignment.updated_at.should > old_timestamp
end
end
context "needs_grading_count_for_user" do
it "should only count submissions in the user's visible section(s)" do
course_with_teacher(:active_all => true)
@section = @course.course_sections.create!(:name => 'section 2')
@user2 = user_with_pseudonym(:active_all => true, :name => 'Student2', :username => 'student2@instructure.com')
@section.enroll_user(@user2, 'StudentEnrollment', 'active')
@user1 = user_with_pseudonym(:active_all => true, :name => 'Student1', :username => 'student1@instructure.com')
@course.enroll_student(@user1).update_attribute(:workflow_state, 'active')
# enroll a section-limited TA
@ta = user_with_pseudonym(:active_all => true, :name => 'TA1', :username => 'ta1@instructure.com')
ta_enrollment = @course.enroll_ta(@ta)
ta_enrollment.limit_privileges_to_course_section = true
ta_enrollment.workflow_state = 'active'
ta_enrollment.save!
# make a submission in each section
@assignment = @course.assignments.create(:title => "some assignment", :submission_types => ['online_text_entry'])
@assignment.submit_homework @user1, :submission_type => "online_text_entry", :body => "o hai"
@assignment.submit_homework @user2, :submission_type => "online_text_entry", :body => "haldo"
@assignment.reload
# check the teacher sees both, the TA sees one
@assignment.needs_grading_count_for_user(@teacher).should eql(2)
@assignment.needs_grading_count_for_user(@ta).should eql(1)
@teacher.assignments_needing_grading.collect(&:id).should == [@assignment.id]
@ta.assignments_needing_grading.collect(&:id).should == [@assignment.id]
# grade an assignment
@assignment.grade_student(@user1, :grade => "1")
@assignment.reload
# check that the numbers changed
@assignment.needs_grading_count_for_user(@teacher).should eql(1)
@assignment.needs_grading_count_for_user(@ta).should eql(0)
@teacher.assignments_needing_grading.collect(&:id).should == [@assignment.id]
@ta.assignments_needing_grading.collect(&:id).should == []
# test limited enrollment in multiple sections
@course.enroll_user(@ta, 'TaEnrollment', :enrollment_state => 'active', :section => @section,
:allow_multiple_enrollments => true, :limit_privileges_to_course_section => true)
@assignment.reload
@assignment.needs_grading_count_for_user(@ta).should eql(1)
@ta.assignments_needing_grading.collect(&:id).should == [@assignment.id]
end
end end
it "should preserve pass/fail with zero points possible" do it "should preserve pass/fail with zero points possible" do