Skip to content

Commit 15868be

Browse files
committed
Faster AssignmentGroupsController#index
refs CNVS-2500, closes CNVS-6577 Test plan: * make sure the assignment groups index api action still works - be sure to include assignments * test with and without overrides * make sure gradebook2 still works (focus on anything related to assignment due dates) Change-Id: I440a8fdeffdb497884356f08c19b5b7792566a3a Reviewed-on: https://gerrit.instructure.com/22102 Reviewed-by: Simon Williams <simon@instructure.com> QA-Review: Simon Williams <simon@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Cameron Matheson <cameron@instructure.com>
1 parent 057e57d commit 15868be

7 files changed

Lines changed: 97 additions & 20 deletions

File tree

app/controllers/assignment_groups_controller.rb

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,38 @@ class AssignmentGroupsController < ApplicationController
5858
#
5959
# @argument include[] ["assignments","discussion_topic"] Associations to include with the group.
6060
# "discussion_topic" is only valid if "assignments" is also included
61+
# @argument override_assignment_dates [Optional, Boolean]
62+
# Apply assignment overrides for each assignment, defaults to true.
6163
#
6264
# @returns [Assignment Group]
6365
def index
64-
@groups = @context.assignment_groups.active
66+
if authorized_action(@context.assignment_groups.new, @current_user, :read)
67+
@groups = @context.assignment_groups.active
6568

66-
params[:include] = Array(params[:include])
67-
if params[:include].include? 'assignments'
68-
params[:include] << "discussion_topic"
69-
@groups = @groups.includes(:assignments => [:rubric, :discussion_topic])
70-
end
69+
params[:include] = Array(params[:include])
70+
if params[:include].include? 'assignments'
71+
assignment_includes = [:rubric, :quiz, :external_tool_tag]
72+
assignment_includes.concat(params[:include] & [:discussion_topic])
73+
@groups = @groups.includes(:active_assignments => assignment_includes)
74+
75+
assignment_descriptions = @groups
76+
.flat_map(&:active_assignments)
77+
.map(&:description)
78+
user_content_attachments = api_bulk_load_user_content_attachments(
79+
assignment_descriptions, @context, @current_user
80+
)
81+
82+
params[:override_assignment_dates] ||= true
83+
override_dates = value_to_boolean(params[:override_assignment_dates])
84+
end
7185

72-
if authorized_action(@context.assignment_groups.new, @current_user, :read)
7386
respond_to do |format|
7487
format.json {
7588
json = @groups.map { |g|
76-
assignment_group_json(g, @current_user, session, params[:include])
89+
g.context = @context
90+
assignment_group_json(g, @current_user, session, params[:include],
91+
override_assignment_dates: override_dates,
92+
preloaded_user_content_attachments: user_content_attachments)
7793
}
7894
render :json => json
7995
}

app/controllers/gradebook2_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def show
1010
per_page = Setting.get_cached('api_max_per_page', '50').to_i
1111
js_env :GRADEBOOK_OPTIONS => {
1212
:chunk_size => Setting.get_cached('gradebook2.submissions_chunk_size', '35').to_i,
13-
:assignment_groups_url => api_v1_course_assignment_groups_url(@context, :include => [:assignments]),
13+
:assignment_groups_url => api_v1_course_assignment_groups_url(@context, :include => [:assignments], :override_assignment_dates => "false"),
1414
:sections_url => api_v1_course_sections_url(@context),
1515
:students_url => api_v1_course_enrollments_url(@context, :include => [:avatar_url], :type => ['StudentEnrollment', 'StudentViewEnrollment'], :per_page => per_page),
1616
:students_url_with_concluded_enrollments => api_v1_course_enrollments_url(@context, :include => [:avatar_url], :type => ['StudentEnrollment', 'StudentViewEnrollment'], :state => ['active', 'invited', 'completed'], :per_page => per_page),

app/models/course.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1734,8 +1734,9 @@ def self.copy_authorized_content(html, to_context, user)
17341734
def turnitin_settings
17351735
# check if somewhere up the account chain turnitin is enabled and
17361736
# has valid settings
1737-
self.account.turnitin_settings
1737+
account.turnitin_settings
17381738
end
1739+
memoize :turnitin_settings
17391740

17401741
def turnitin_pledge
17411742
self.account.closest_turnitin_pledge

lib/api.rb

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,28 @@ def media_comment_json(media_object_or_hash)
255255
# a hash of allowed html attributes that represent urls, like { 'a' => ['href'], 'img' => ['src'] }
256256
UrlAttributes = Instructure::SanitizeField::SANITIZE[:protocols].inject({}) { |h,(k,v)| h[k] = v.keys; h }
257257

258-
def api_user_content(html, context = @context, user = @current_user)
258+
def api_bulk_load_user_content_attachments(htmls, context = @context, user = @current_user)
259+
rewriter = UserContent::HtmlRewriter.new(context, user)
260+
attachment_ids = []
261+
rewriter.set_handler('files') do |m|
262+
attachment_ids << m.obj_id if m.obj_id
263+
end
264+
265+
htmls.each { |html| rewriter.translate_content(html) }
266+
267+
if attachment_ids.blank?
268+
{}
269+
else
270+
attachments = if context.is_a?(User) || context.nil?
271+
Attachment.where(id: attachment_ids)
272+
else
273+
context.attachments.where(id: attachment_ids)
274+
end
275+
attachments.index_by(&:id)
276+
end
277+
end
278+
279+
def api_user_content(html, context = @context, user = @current_user, preloaded_attachments = {})
259280
return html if html.blank?
260281

261282
# if we're a controller, use the host of the request, otherwise let HostUrl
@@ -271,11 +292,12 @@ def api_user_content(html, context = @context, user = @current_user)
271292
rewriter = UserContent::HtmlRewriter.new(context, user)
272293
rewriter.set_handler('files') do |match|
273294
if match.obj_id
274-
if match.obj_class == Attachment && context && !context.is_a?(User)
275-
obj = context.attachments.find(match.obj_id) rescue nil
276-
else
277-
obj = match.obj_class.find_by_id(match.obj_id)
278-
end
295+
obj = preloaded_attachments[match.obj_id]
296+
obj ||= if context.is_a?(User) || context.nil?
297+
Attachment.find_by_id(match.obj_id)
298+
else
299+
context.attachments.find_by_id(match.obj_id)
300+
end
279301
end
280302
next unless obj && rewriter.user_can_view_content?(obj)
281303

lib/api/v1/assignment.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ def assignment_json(assignment, user, session, opts = {})
7676

7777
# use already generated hash['description'] because it is filtered by
7878
# Assignment#filter_attributes_for_user when the assignment is locked
79-
hash['description'] = api_user_content(hash['description'], @context || assignment.context)
79+
hash['description'] = api_user_content(hash['description'],
80+
@context || assignment.context,
81+
user,
82+
opts[:preloaded_user_content_attachments] || {})
8083
hash['muted'] = assignment.muted?
8184
hash['html_url'] = course_assignment_url(assignment.context_id, assignment)
8285

lib/api/v1/assignment_group.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,28 @@ module Api::V1::AssignmentGroup
2727
rules
2828
)
2929

30-
def assignment_group_json(group, user, session, includes = [])
30+
def assignment_group_json(group, user, session, includes = [], opts = {})
3131
includes ||= []
32+
opts.reverse_merge! override_assignment_dates: true
3233

3334
hash = api_json(group, user, session,
3435
:only => %w(id name position group_weight))
3536
hash['rules'] = group.rules_hash
3637

3738
if includes.include?('assignments')
38-
hash['assignments'] = group.assignments.active.map { |a|
39-
assignment_json(a, user, session, include_discussion_topic: includes.include?('discussion_topic'))
39+
include_discussion_topic = includes.include?('discussion_topic')
40+
user_content_attachments = opts[:preloaded_user_content_attachments]
41+
user_content_attachments ||= api_bulk_load_user_content_attachments(
42+
group.active_assignments.map(&:description),
43+
group.context,
44+
user
45+
)
46+
hash['assignments'] = group.active_assignments.map { |a|
47+
a.context = group.context
48+
assignment_json(a, user, session,
49+
include_discussion_topic: include_discussion_topic,
50+
override_dates: opts[:override_assignment_dates],
51+
preloaded_user_content_attachments: user_content_attachments)
4052
}
4153
end
4254

spec/apis/user_content_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,4 +389,27 @@ class Tester
389389
doc.at_css('a')['href'].should == url
390390
end
391391
end
392+
393+
describe ".api_bulk_load_user_content_attachments" do
394+
it "returns a hash of assignment_id => assignment" do
395+
course_with_teacher(:active_all => true)
396+
a1, a2, a3 = attachment_model, attachment_model, attachment_model
397+
html1, html2 = <<-HTML1, <<-HTML2
398+
<a href="/courses/#{@course.id}/files/#{a1.id}/download">uh...</a>
399+
<img src="/courses/#{@course.id}/files/#{a2.id}/download">
400+
HTML1
401+
<a href="/courses/#{@course.id}/files/#{a3.id}/download">Hi</a>
402+
HTML2
403+
404+
class ApiClass
405+
include Api
406+
end
407+
408+
ApiClass.new.api_bulk_load_user_content_attachments(
409+
[html1, html2],
410+
@course,
411+
@teacher
412+
).should == {a1.id => a1, a2.id => a2, a3.id => a3}
413+
end
414+
end
392415
end

0 commit comments

Comments
 (0)