Skip to content

Instantly share code, notes, and snippets.

@antoviaque
Created July 17, 2014 07:12
Show Gist options
  • Select an option

  • Save antoviaque/c43617be44c9fc5ee6df to your computer and use it in GitHub Desktop.

Select an option

Save antoviaque/c43617be44c9fc5ee6df to your computer and use it in GitHub Desktop.
diff --cc api/search.rb
index a49c2f4,a6815f3..0000000
--- a/api/search.rb
+++ b/api/search.rb
@@@ -1,88 -1,45 +1,103 @@@
require 'new_relic/agent/method_tracer'
get "#{APIPREFIX}/search/threads" do
+ local_params = params # Necessary for params to be available inside blocks
+ sort_criteria = get_sort_criteria(local_params)
- sort_key_mapper = {
- "date" => :created_at,
- "activity" => :last_activity_at,
- "votes" => :votes_point,
- "comments" => :comment_count,
- }
-
- sort_order_mapper = {
- "desc" => :desc,
- "asc" => :asc,
- }
-
- sort_key = sort_key_mapper[params["sort_key"]]
- sort_order = sort_order_mapper[params["sort_order"]]
-
- sort_keyword_valid = (!params["sort_key"] && !params["sort_order"] || sort_key && sort_order)
-
- if (!params["text"] && !params["commentable_ids"]) || !sort_keyword_valid
+ search_text = local_params["text"]
+ if !search_text || !sort_criteria
{}.to_json
else
++<<<<<<< HEAD
+ page = (local_params["page"] || DEFAULT_PAGE).to_i
+ per_page = (local_params["per_page"] || DEFAULT_PER_PAGE).to_i
+
+ # Because threads and comments are currently separate unrelated documents in
+ # Elasticsearch, we must first query for all matching documents, then
+ # extract the set of thread ids, and then sort the threads by the specified
+ # criteria and paginate. For performance reasons, we currently limit the
+ # number of documents considered (ordered by update recency), which means
+ # that matching threads can be missed if the search terms are very common.
+
+ get_matching_thread_ids = lambda do |search_text|
+ self.class.trace_execution_scoped(["Custom/get_search_threads/es_search"]) do
+ search = Tire.search Content::ES_INDEX_NAME do
+ query do
+ match [:title, :body], search_text, :operator => "AND"
+ filtered do
+ filter :term, :commentable_id => local_params["commentable_id"] if local_params["commentable_id"]
+ filter :terms, :commentable_id => local_params["commentable_ids"].split(",") if local_params["commentable_ids"]
+ filter :term, :course_id => local_params["course_id"] if local_params["course_id"]
+ if local_params["group_id"]
+ filter :or, [
+ {:not => {:exists => {:field => :group_id}}},
+ {:term => {:group_id => local_params["group_id"]}}
+ ]
+ end
+ end
+ end
+ sort do
+ by "updated_at", "desc"
+ end
+ size CommentService.config["max_deep_search_comment_count"].to_i
+ end
+ thread_ids = Set.new
+ search.results.each do |content|
+ case content.type
+ when "comment_thread"
+ thread_ids.add(content.id)
+ when "comment"
+ thread_ids.add(content.comment_thread_id)
+ end
+ end
+ thread_ids
+ end
+ end
++=======
+ page = (params["page"] || DEFAULT_PAGE).to_i
+ per_page = (params["per_page"] || DEFAULT_PER_PAGE).to_i
+ # for multi commentable searching
+ params["commentable_ids"] = params["commentable_ids"].split(',') if params["commentable_ids"]
+ params["group_ids"] = params["group_ids"].split(',') if params["group_ids"]
+
+ options = {
+ sort_key: sort_key,
+ sort_order: sort_order,
+ page: page,
+ per_page: per_page,
+ }
++>>>>>>> 6095f2c... Add the ability to query multiple group ids
- results = CommentThread.perform_search(params, options)
+ # Sadly, Elasticsearch does not have a facility for computing suggestions
+ # with respect to a filter. It would be expensive to determine the best
+ # suggestion with respect to our filter parameters, so we simply re-query
+ # with the top suggestion. If that has no results, then we return no results
+ # and no correction.
+ thread_ids = get_matching_thread_ids.call(search_text)
+ corrected_text = nil
+ if thread_ids.empty?
+ suggest = Tire.suggest Content::ES_INDEX_NAME do
+ suggestion "" do
+ text search_text
+ phrase :_all
+ end
+ end
+ corrected_text = suggest.results.texts.first
+ thread_ids = get_matching_thread_ids.call(corrected_text) if corrected_text
+ corrected_text = nil if thread_ids.empty?
+ end
- if page > results.total_pages #TODO find a better way for this
- results = CommentThread.perform_search(params, options.merge(page: results.total_pages))
+ results = nil
+ self.class.trace_execution_scoped(["Custom/get_search_threads/mongo_sort_page"]) do
+ results = CommentThread.
+ where(:id.in => thread_ids.to_a).
+ order_by(sort_criteria).
+ page(page).
+ per(per_page).
+ to_a
end
+ total_results = thread_ids.size
+ num_pages = (total_results + per_page - 1) / per_page
if results.length == 0
collection = []
diff --cc models/comment_thread.rb
index dbbcdf7,3db513d..0000000
--- a/models/comment_thread.rb
+++ b/models/comment_thread.rb
@@@ -76,6 -74,121 +76,124 @@@ class CommentThread < Conten
c
end
++<<<<<<< HEAD
++=======
+ def self.perform_search(params, options={})
+
+ page = [1, options[:page] || 1].max
+ per_page = options[:per_page] || 20
+ sort_key = options[:sort_key]
+ sort_order = options[:sort_order]
+
+
+ #GET /api/v1/search/threads?user_id=1&recursive=False&sort_key=date&│[2013-06-28 10:16:46,104][INFO ][plugins ] [Glamor] loaded [], sites []
+ #text=response&sort_order=desc&course_id=HarvardX%2FHLS1xD%2FCopyright&per_page=20&api_key=PUT_YOUR_API_KE│T1GYWxzZSZzb3J0X2tleT1kYXRlJnRleHQ9cmVzcG9uc2Umc29ydF9vcmRlcj1kZXNjJmNvdXJzZV9pZA==: initialized
+ #Y_HERE&page=1
+
+ #KChugh - Unfortunately, there's no algorithmically nice way to handle pagination with
+ #stitching together Comments and CommentThreads, because there is no determinstic relationship
+ #between the ordinality of comments and threads.
+ #the best solution is to find all of the thread ids for matching comment hits, and union them
+ #with the comment thread query, however, Tire does not support ORing a query key with a term filter
+
+ #so the 3rd best solution is to run two Tire searches (3 actually, one to query the comments, one to query the threads based on
+ #thread ids and the original thread search) and merge the results, uniqifying the results in the process.
+
+ #so first, find the comment threads associated with comments that hit the query
+
+ search = Tire::Search::Search.new 'comment_threads'
+
+ search.query {|query| query.match [:title, :body], params["text"]} if params["text"]
+ search.highlight({title: { number_of_fragments: 0 } } , {body: { number_of_fragments: 0 } }, options: { tag: "<highlight>" })
+ search.filter(:term, commentable_id: params["commentable_id"]) if params["commentable_id"]
+ search.filter(:terms, commentable_id: params["commentable_ids"]) if params["commentable_ids"]
+ search.filter(:term, course_id: params["course_id"]) if params["course_id"]
+
+ group_ids = []
+ group_ids.concat(params["group_ids"]) if params["group_ids"]
+ group_ids << params["group_id"] if params["group_id"]
+
+ if not group_ids.empty?
+ search.filter :or, [
+ {:not => {:exists => {:field => :group_id}}},
+ {:terms => {:group_id => group_ids}}
+ ]
+ end
+
+ search.sort {|sort| sort.by sort_key, sort_order} if sort_key && sort_order #TODO should have search option 'auto sort or sth'
+
+ #again, b/c there is no relationship in ordinality, we cannot paginate if it's a text query
+
+ if not params["text"]
+ search.size per_page
+ search.from per_page * (page - 1)
+ end
+
+ results = search.results
+
+ #if this is a search query, then also search the comments and harvest the matching comments
+ if params["text"]
+
+ search = Tire::Search::Search.new 'comments'
+ search.query {|query| query.match :body, params["text"]} if params["text"]
+ search.filter(:term, course_id: params["course_id"]) if params["course_id"]
+ search.size CommentService.config["max_deep_search_comment_count"].to_i
+
+ #unforutnately, we cannot paginate here, b/c we don't know how the ordinality is totally
+ #unrelated to that of threads
+
+ c_results = comment_ids = comments = thread_ids = nil
+ self.class.trace_execution_scoped(['Custom/perform_search/collect_comment_search_results']) do
+ c_results = search.results
+ comment_ids = c_results.collect{|c| c.id}.uniq
+ end
+ self.class.trace_execution_scoped(['Custom/perform_search/collect_comment_thread_ids']) do
+ comments = Comment.where(:id.in => comment_ids)
+ thread_ids = comments.collect{|c| c.comment_thread_id}
+ end
+
+ #thread_ids = c_results.collect{|c| c.comment_thread_id}
+ #as soon as we can add comment thread id to the ES index, via Tire updgrade, we'll
+ #use ES instead of mongo to collect the thread ids
+
+ #use the elasticsearch index instead to avoid DB hit
+
+ self.class.trace_execution_scoped(['Custom/perform_search/collect_unique_thread_ids']) do
+ original_thread_ids = results.collect{|r| r.id}
+
+ #now add the original search thread ids
+ thread_ids += original_thread_ids
+
+ thread_ids = thread_ids.uniq
+ end
+
+ #now run one more search to harvest the threads and filter by group
+ search = Tire::Search::Search.new 'comment_threads'
+ search.filter(:terms, :thread_id => thread_ids)
+ search.filter(:terms, commentable_id: params["commentable_ids"]) if params["commentable_ids"]
+ search.filter(:term, course_id: params["course_id"]) if params["course_id"]
+
+ search.size per_page
+ search.from per_page * (page - 1)
+
+ if params["group_id"]
+
+ search.filter :or, [
+ {:not => {:exists => {:field => :group_id}}},
+ {:term => {:group_id => params["group_id"]}}
+
+ ]
+ end
+
+ search.sort {|sort| sort.by sort_key, sort_order} if sort_key && sort_order
+ results = search.results
+
+ end
+
+ results
+ end
+
++>>>>>>> 6095f2c... Add the ability to query multiple group ids
def activity_since(from_time=nil)
if from_time
activities.where(:created_at => {:$gte => from_time})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment