add search_term helper for api indexes
test plan: * test that the 'search_term' argument can be used (as documented) in the api 'index' actions for the following: - assignments - discussion topics - external tools - quizzes and they behave identically to the wiki pages api closes #CNVS-7047 Change-Id: I133a9571f134563deb06cd62956238d1e4a57d22 Reviewed-on: https://gerrit.instructure.com/22587 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Jeremy Stanley <jeremy@instructure.com> QA-Review: August Thornton <august@instructure.com>
This commit is contained in:
parent
2dcf0adb32
commit
9172c8c5ca
|
@ -291,6 +291,7 @@ class AssignmentsApiController < ApplicationController
|
||||||
# @API List assignments
|
# @API List assignments
|
||||||
# Returns the list of assignments for the current context.
|
# Returns the list of assignments for the current context.
|
||||||
# @argument include[] ["submission"] Associations to include with the assignment.
|
# @argument include[] ["submission"] Associations to include with the assignment.
|
||||||
|
# @argument search_term (optional) The partial title of the assignments to match and return.
|
||||||
# @argument override_assignment_dates [Optional, Boolean]
|
# @argument override_assignment_dates [Optional, Boolean]
|
||||||
# Apply assignment overrides for each assignment, defaults to true.
|
# Apply assignment overrides for each assignment, defaults to true.
|
||||||
# @returns [Assignment]
|
# @returns [Assignment]
|
||||||
|
@ -300,6 +301,8 @@ class AssignmentsApiController < ApplicationController
|
||||||
includes(:assignment_group, :rubric_association, :rubric).
|
includes(:assignment_group, :rubric_association, :rubric).
|
||||||
reorder("assignment_groups.position, assignments.position")
|
reorder("assignment_groups.position, assignments.position")
|
||||||
|
|
||||||
|
@assignments = Assignment.search_by_attribute(@assignments, :title, params[:search_term])
|
||||||
|
|
||||||
#fake assignment used for checking if the @current_user can read unpublished assignments
|
#fake assignment used for checking if the @current_user can read unpublished assignments
|
||||||
fake = @context.assignments.new
|
fake = @context.assignments.new
|
||||||
fake.workflow_state = 'unpublished'
|
fake.workflow_state = 'unpublished'
|
||||||
|
|
|
@ -156,6 +156,7 @@ class DiscussionTopicsController < ApplicationController
|
||||||
# @argument order_by Determines the order of the discussion topic list. May be one of "position", or "recent_activity". Defaults to "position".
|
# @argument order_by Determines the order of the discussion topic list. May be one of "position", or "recent_activity". Defaults to "position".
|
||||||
# @argument scope [Optional, "locked"|"unlocked"] Only return discussion topics in the given state. Defaults to including locked and unlocked topics. Filtering is done after pagination, so pages may be smaller than requested if topics are filtered
|
# @argument scope [Optional, "locked"|"unlocked"] Only return discussion topics in the given state. Defaults to including locked and unlocked topics. Filtering is done after pagination, so pages may be smaller than requested if topics are filtered
|
||||||
# @argument only_announcements [Optional] Boolean, return announcements instead of discussion topics. Defaults to false
|
# @argument only_announcements [Optional] Boolean, return announcements instead of discussion topics. Defaults to false
|
||||||
|
# @argument search_term (optional) The partial title of the discussion topics to match and return.
|
||||||
#
|
#
|
||||||
# @example_request
|
# @example_request
|
||||||
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics \
|
# curl https://<canvas>/api/v1/courses/<course_id>/discussion_topics \
|
||||||
|
@ -174,6 +175,8 @@ class DiscussionTopicsController < ApplicationController
|
||||||
|
|
||||||
scope = params[:order_by] == 'recent_activity' ? scope.by_last_reply_at : scope.by_position
|
scope = params[:order_by] == 'recent_activity' ? scope.by_last_reply_at : scope.by_position
|
||||||
|
|
||||||
|
scope = DiscussionTopic.search_by_attribute(scope, :title, params[:search_term])
|
||||||
|
|
||||||
@topics = Api.paginate(scope, self, topic_pagination_url)
|
@topics = Api.paginate(scope, self, topic_pagination_url)
|
||||||
@topics.reject! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'unlocked'
|
@topics.reject! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'unlocked'
|
||||||
@topics.select! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'locked'
|
@topics.select! { |t| t.locked? || t.locked_for?(@current_user) } if params[:scope] == 'locked'
|
||||||
|
|
|
@ -31,6 +31,7 @@ class ExternalToolsController < ApplicationController
|
||||||
# Returns the paginated list of external tools for the current context.
|
# Returns the paginated list of external tools for the current context.
|
||||||
# See the get request docs for a single tool for a list of properties on an external tool.
|
# See the get request docs for a single tool for a list of properties on an external tool.
|
||||||
#
|
#
|
||||||
|
# @argument search_term (optional) The partial name of the tools to match and return.
|
||||||
#
|
#
|
||||||
# @example_response
|
# @example_response
|
||||||
# [
|
# [
|
||||||
|
@ -70,6 +71,7 @@ class ExternalToolsController < ApplicationController
|
||||||
else
|
else
|
||||||
@tools = @context.context_external_tools.active
|
@tools = @context.context_external_tools.active
|
||||||
end
|
end
|
||||||
|
@tools = ContextExternalTool.search_by_attribute(@tools, :name, params[:search_term])
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
@tools = Api.paginate(@tools, self, tool_pagination_url)
|
@tools = Api.paginate(@tools, self, tool_pagination_url)
|
||||||
format.json {render :json => external_tools_json(@tools, @context, @current_user, session)}
|
format.json {render :json => external_tools_json(@tools, @context, @current_user, session)}
|
||||||
|
|
|
@ -133,6 +133,8 @@ class QuizzesApiController < ApplicationController
|
||||||
#
|
#
|
||||||
# Returns the list of Quizzes in this course.
|
# Returns the list of Quizzes in this course.
|
||||||
#
|
#
|
||||||
|
# @argument search_term (optional) The partial title of the quizzes to match and return.
|
||||||
|
#
|
||||||
# @example_request
|
# @example_request
|
||||||
# curl https://<canvas>/api/v1/courses/<course_id>/quizzes \
|
# curl https://<canvas>/api/v1/courses/<course_id>/quizzes \
|
||||||
# -H 'Authorization: Bearer <token>'
|
# -H 'Authorization: Bearer <token>'
|
||||||
|
@ -141,7 +143,8 @@ class QuizzesApiController < ApplicationController
|
||||||
def index
|
def index
|
||||||
if authorized_action(@context, @current_user, :read) && tab_enabled?(@context.class::TAB_QUIZZES)
|
if authorized_action(@context, @current_user, :read) && tab_enabled?(@context.class::TAB_QUIZZES)
|
||||||
api_route = polymorphic_url([:api, :v1, @context, :quizzes])
|
api_route = polymorphic_url([:api, :v1, @context, :quizzes])
|
||||||
@quizzes = Api.paginate(@context.quizzes.active, self, api_route)
|
scope = Quiz.search_by_attribute(@context.quizzes.active, :title, params[:search_term])
|
||||||
|
@quizzes = Api.paginate(scope, self, api_route)
|
||||||
render :json => quizzes_json(@quizzes, @context, @current_user, session)
|
render :json => quizzes_json(@quizzes, @context, @current_user, session)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,6 +26,7 @@ class Assignment < ActiveRecord::Base
|
||||||
include Mutable
|
include Mutable
|
||||||
include ContextModuleItem
|
include ContextModuleItem
|
||||||
include DatesOverridable
|
include DatesOverridable
|
||||||
|
include SearchTermHelper
|
||||||
|
|
||||||
attr_accessible :title, :name, :description, :due_at, :points_possible,
|
attr_accessible :title, :name, :description, :due_at, :points_possible,
|
||||||
:min_score, :max_score, :mastery_score, :grading_type, :submission_types,
|
:min_score, :max_score, :mastery_score, :grading_type, :submission_types,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
class ContextExternalTool < ActiveRecord::Base
|
class ContextExternalTool < ActiveRecord::Base
|
||||||
include Workflow
|
include Workflow
|
||||||
|
include SearchTermHelper
|
||||||
|
|
||||||
has_many :content_tags, :as => :content
|
has_many :content_tags, :as => :content
|
||||||
belongs_to :context, :polymorphic => true
|
belongs_to :context, :polymorphic => true
|
||||||
belongs_to :cloned_item
|
belongs_to :cloned_item
|
||||||
|
|
|
@ -24,6 +24,7 @@ class DiscussionTopic < ActiveRecord::Base
|
||||||
include CopyAuthorizedLinks
|
include CopyAuthorizedLinks
|
||||||
include TextHelper
|
include TextHelper
|
||||||
include ContextModuleItem
|
include ContextModuleItem
|
||||||
|
include SearchTermHelper
|
||||||
|
|
||||||
attr_accessible :title, :message, :user, :delayed_post_at, :lock_at, :assignment,
|
attr_accessible :title, :message, :user, :delayed_post_at, :lock_at, :assignment,
|
||||||
:plaintext_message, :podcast_enabled, :podcast_has_student_posts,
|
:plaintext_message, :podcast_enabled, :podcast_has_student_posts,
|
||||||
|
|
|
@ -26,6 +26,7 @@ class Quiz < ActiveRecord::Base
|
||||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||||
include ContextModuleItem
|
include ContextModuleItem
|
||||||
include DatesOverridable
|
include DatesOverridable
|
||||||
|
include SearchTermHelper
|
||||||
|
|
||||||
attr_accessible :title, :description, :points_possible, :assignment_id, :shuffle_answers,
|
attr_accessible :title, :description, :points_possible, :assignment_id, :shuffle_answers,
|
||||||
:show_correct_answers, :time_limit, :allowed_attempts, :scoring_policy, :quiz_type,
|
:show_correct_answers, :time_limit, :allowed_attempts, :scoring_policy, :quiz_type,
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
class AddGistIndexesForApiSearch < ActiveRecord::Migration
|
||||||
|
self.transactional = false
|
||||||
|
tag :predeploy
|
||||||
|
|
||||||
|
def self.up
|
||||||
|
if is_postgres?
|
||||||
|
connection.transaction(:requires_new => true) do
|
||||||
|
begin
|
||||||
|
execute('create extension if not exists pg_trgm;')
|
||||||
|
rescue ActiveRecord::StatementInvalid
|
||||||
|
raise ActiveRecord::Rollback
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if has_postgres_proc?('show_trgm')
|
||||||
|
concurrently = " CONCURRENTLY" if connection.open_transactions == 0
|
||||||
|
execute("create index#{concurrently} index_trgm_context_external_tools_name on context_external_tools USING gist(lower(name) gist_trgm_ops);")
|
||||||
|
execute("create index#{concurrently} index_trgm_assignments_title on assignments USING gist(lower(title) gist_trgm_ops);")
|
||||||
|
execute("create index#{concurrently} index_trgm_quizzes_title on quizzes USING gist(lower(title) gist_trgm_ops);")
|
||||||
|
execute("create index#{concurrently} index_trgm_discussion_topics_title on discussion_topics USING gist(lower(title) gist_trgm_ops);")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
if is_postgres?
|
||||||
|
execute('drop index if exists index_trgm_context_external_tools_name;')
|
||||||
|
execute('drop index if exists index_trgm_assignments_title;')
|
||||||
|
execute('drop index if exists index_trgm_quizzes_title;')
|
||||||
|
execute('drop index if exists index_trgm_discussion_topics_title;')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -116,6 +116,24 @@ describe AssignmentsApiController, :type => :integration do
|
||||||
assignment4)
|
assignment4)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should search for assignments by title" do
|
||||||
|
course_with_teacher(:active_all => true)
|
||||||
|
2.times {|i| @course.assignments.create!(:title => "first_#{i}") }
|
||||||
|
ids = @course.assignments.map(&:id)
|
||||||
|
2.times {|i| @course.assignments.create!(:title => "second_#{i}") }
|
||||||
|
|
||||||
|
json = api_call(:get,
|
||||||
|
"/api/v1/courses/#{@course.id}/assignments.json?search_term=fir",
|
||||||
|
{
|
||||||
|
:controller => 'assignments_api',
|
||||||
|
:action => 'index',
|
||||||
|
:format => 'json',
|
||||||
|
:course_id => @course.id.to_s,
|
||||||
|
:search_term => 'fir'
|
||||||
|
})
|
||||||
|
json.map{|h| h['id']}.sort.should == ids.sort
|
||||||
|
end
|
||||||
|
|
||||||
it "should return the assignments list with API-formatted Rubric data" do
|
it "should return the assignments list with API-formatted Rubric data" do
|
||||||
# the API changes the structure of the data quite a bit, to hide
|
# the API changes the structure of the data quite a bit, to hide
|
||||||
# implementation details and ease API use.
|
# implementation details and ease API use.
|
||||||
|
|
|
@ -303,6 +303,17 @@ describe DiscussionTopicsController, :type => :integration do
|
||||||
json.last.should == @response_json.merge("subscribed" => @sub.subscribed?(@user))
|
json.last.should == @response_json.merge("subscribed" => @sub.subscribed?(@user))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should search discussion topics by title" do
|
||||||
|
ids = @course.discussion_topics.map(&:id)
|
||||||
|
create_topic(@course, :title => "ignore me", :message => "<p>i'm subversive</p>")
|
||||||
|
create_topic(@course, :title => "ignore me2", :message => "<p>i'm subversive</p>")
|
||||||
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/discussion_topics.json?search_term=topic",
|
||||||
|
{:controller => 'discussion_topics', :action => 'index', :format => 'json', :course_id => @course.id.to_s,
|
||||||
|
:search_term => 'topic'})
|
||||||
|
|
||||||
|
json.map{|h| h['id']}.sort.should == ids.sort
|
||||||
|
end
|
||||||
|
|
||||||
it "should order topics by descending position by default" do
|
it "should order topics by descending position by default" do
|
||||||
@topic2 = create_topic(@course, :title => "Topic 2", :message => "<p>content here</p>")
|
@topic2 = create_topic(@course, :title => "Topic 2", :message => "<p>content here</p>")
|
||||||
@topic3 = create_topic(@course, :title => "Topic 3", :message => "<p>content here</p>")
|
@topic3 = create_topic(@course, :title => "Topic 3", :message => "<p>content here</p>")
|
||||||
|
|
|
@ -37,6 +37,10 @@ describe ExternalToolsController, :type => :integration do
|
||||||
index_call(@course)
|
index_call(@course)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should search for external tools by name" do
|
||||||
|
search_call(@course)
|
||||||
|
end
|
||||||
|
|
||||||
it "should create an external tool" do
|
it "should create an external tool" do
|
||||||
create_call(@course)
|
create_call(@course)
|
||||||
end
|
end
|
||||||
|
@ -87,6 +91,10 @@ describe ExternalToolsController, :type => :integration do
|
||||||
index_call(@account, "account")
|
index_call(@account, "account")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should search for external tools by name" do
|
||||||
|
search_call(@account, "account")
|
||||||
|
end
|
||||||
|
|
||||||
it "should create an external tool" do
|
it "should create an external tool" do
|
||||||
create_call(@account, "account")
|
create_call(@account, "account")
|
||||||
end
|
end
|
||||||
|
@ -144,6 +152,19 @@ describe ExternalToolsController, :type => :integration do
|
||||||
json.first.diff(example_json(et)).should == {}
|
json.first.diff(example_json(et)).should == {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def search_call(context, type="course")
|
||||||
|
2.times { |i| context.context_external_tools.create!(:name => "first_#{i}", :consumer_key => "fakefake", :shared_secret => "sofakefake", :url => "http://www.example.com/ims/lti") }
|
||||||
|
ids = context.context_external_tools.map(&:id)
|
||||||
|
|
||||||
|
2.times { |i| context.context_external_tools.create!(:name => "second_#{i}", :consumer_key => "fakefake", :shared_secret => "sofakefake", :url => "http://www.example.com/ims/lti") }
|
||||||
|
|
||||||
|
json = api_call(:get, "/api/v1/#{type}s/#{context.id}/external_tools.json?search_term=fir",
|
||||||
|
{:controller => 'external_tools', :action => 'index', :format => 'json',
|
||||||
|
:"#{type}_id" => context.id.to_s, :search_term => 'fir'})
|
||||||
|
|
||||||
|
json.map{|h| h['id']}.sort.should == ids.sort
|
||||||
|
end
|
||||||
|
|
||||||
def create_call(context, type="course")
|
def create_call(context, type="course")
|
||||||
json = api_call(:post, "/api/v1/#{type}s/#{context.id}/external_tools.json",
|
json = api_call(:post, "/api/v1/#{type}s/#{context.id}/external_tools.json",
|
||||||
{:controller => 'external_tools', :action => 'create', :format => 'json',
|
{:controller => 'external_tools', :action => 'create', :format => 'json',
|
||||||
|
|
|
@ -51,6 +51,18 @@ describe QuizzesApiController, :type => :integration do
|
||||||
quiz_ids.should == quizzes.map(&:id)
|
quiz_ids.should == quizzes.map(&:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should search for quizzes by title" do
|
||||||
|
2.times{ |i| @course.quizzes.create! :title => "first_#{i}" }
|
||||||
|
ids = @course.quizzes.map(&:id)
|
||||||
|
2.times{ |i| @course.quizzes.create! :title => "second_#{i}" }
|
||||||
|
|
||||||
|
json = api_call(:get, "/api/v1/courses/#{@course.id}/quizzes?search_term=fir",
|
||||||
|
:controller=>"quizzes_api", :action=>"index", :format=>"json", :course_id=>"#{@course.id}",
|
||||||
|
:search_term => 'fir')
|
||||||
|
|
||||||
|
json.map{|h| h['id'] }.sort.should == ids.sort
|
||||||
|
end
|
||||||
|
|
||||||
it "should return unauthorized if the quiz tab is disabled" do
|
it "should return unauthorized if the quiz tab is disabled" do
|
||||||
@course.tab_configuration = [ { :id => Course::TAB_QUIZZES, :hidden => true } ]
|
@course.tab_configuration = [ { :id => Course::TAB_QUIZZES, :hidden => true } ]
|
||||||
student_in_course(:active_all => true, :course => @course)
|
student_in_course(:active_all => true, :course => @course)
|
||||||
|
|
Loading…
Reference in New Issue