Commit 1235e147 by Diana Huang Committed by GitHub

Merge pull request #225 from edx/diana/elasticsearch-update

Diana/elasticsearch update
parents 276448e2 e2053333
...@@ -12,6 +12,7 @@ gem 'bundler' ...@@ -12,6 +12,7 @@ gem 'bundler'
gem 'rake' gem 'rake'
gem 'sinatra' gem 'sinatra'
gem 'sinatra-param', '~> 1.4'
gem 'yajl-ruby' gem 'yajl-ruby'
......
...@@ -154,6 +154,8 @@ GEM ...@@ -154,6 +154,8 @@ GEM
rack (~> 1.3, >= 1.3.6) rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2) rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3) tilt (~> 1.3, >= 1.3.3)
sinatra-param (1.4.0)
sinatra (~> 1.3)
slop (3.3.2) slop (3.3.2)
thor (0.16.0) thor (0.16.0)
thread_safe (0.3.5) thread_safe (0.3.5)
...@@ -215,6 +217,7 @@ DEPENDENCIES ...@@ -215,6 +217,7 @@ DEPENDENCIES
rs_voteable_mongo! rs_voteable_mongo!
rspec (~> 2.11.0) rspec (~> 2.11.0)
sinatra sinatra
sinatra-param (~> 1.4)
unicorn unicorn
webmock (~> 1.22) webmock (~> 1.22)
will_paginate_mongoid (~> 2.0) will_paginate_mongoid (~> 2.0)
......
...@@ -125,14 +125,20 @@ def get_threads(context, group_ids, local_params, search_text) ...@@ -125,14 +125,20 @@ def get_threads(context, group_ids, local_params, search_text)
result_obj.to_json result_obj.to_json
end end
error Sinatra::Param::InvalidParameterError do
# NOTE (CCB): The current behavior of the service is to return a seemingly positive response
# for an invalid request. In the future the API's contract should be modified so that HTTP 400
# is returned. This informs the client that the request was invalid, rather than having to guess
# about an empty response body.
[200, '{}']
end
get "#{APIPREFIX}/search/threads" do get "#{APIPREFIX}/search/threads" do
param :text, String, required: true
param :context, String, default: 'course'
param :sort_key, String, in: %w(activity comments date votes), transform: :downcase
local_params = params # Necessary for params to be available inside blocks local_params = params # Necessary for params to be available inside blocks
group_ids = get_group_ids_from_params(local_params) group_ids = get_group_ids_from_params(local_params)
context = local_params["context"] ? local_params["context"] : "course" get_threads(params[:context], group_ids, local_params, params[:text])
search_text = local_params["text"]
if !search_text
'{}'
else
get_threads(context, group_ids, local_params, search_text)
end
end end
...@@ -27,14 +27,34 @@ if ENV["ENABLE_GC_PROFILER"] ...@@ -27,14 +27,34 @@ if ENV["ENABLE_GC_PROFILER"]
GC::Profiler.enable GC::Profiler.enable
end end
def get_logger(progname, threshold=nil)
logger = Logger.new(STDERR)
logger.progname = progname
logger.level = threshold || Logger::INFO
logger
end
application_yaml = ERB.new(File.read("config/application.yml")).result() application_yaml = ERB.new(File.read("config/application.yml")).result()
CommentService.config = YAML.load(application_yaml).with_indifferent_access CommentService.config = YAML.load(application_yaml).with_indifferent_access
# Raise sinatra-param exceptions so that we can process, and respond to, them appropriately
set :raise_sinatra_param_exceptions, true
# Setup Mongo
Mongoid.load!("config/mongoid.yml", environment) Mongoid.load!("config/mongoid.yml", environment)
Mongoid.logger.level = Logger::INFO Mongoid.logger.level = Logger::INFO
Mongo::Logger.logger.level = ENV["ENABLE_MONGO_DEBUGGING"] ? Logger::DEBUG : Logger::INFO Mongo::Logger.logger.level = ENV["ENABLE_MONGO_DEBUGGING"] ? Logger::DEBUG : Logger::INFO
# set up i18n # Setup Elasticsearch
# NOTE (CCB): If you want to see all data sent to Elasticsearch (e.g. for debugging purposes), set the tracer argument
# to the value of a logger.
# Example: Elascisearch.Client.new(tracer: get_logger('elasticsearch.tracer'))
Elasticsearch::Model.client = Elasticsearch::Client.new(
host: CommentService.config[:elasticsearch_server],
logger: get_logger('elasticsearch', Logger::WARN)
)
# Setup i18n
I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml').to_s] I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'locale', '*.yml').to_s]
I18n.default_locale = CommentService.config[:default_locale] I18n.default_locale = CommentService.config[:default_locale]
I18n.enforce_available_locales = false I18n.enforce_available_locales = false
...@@ -51,7 +71,6 @@ Dir[File.dirname(__FILE__) + '/lib/**/*.rb'].each { |file| require file } ...@@ -51,7 +71,6 @@ Dir[File.dirname(__FILE__) + '/lib/**/*.rb'].each { |file| require file }
Dir[File.dirname(__FILE__) + '/models/*.rb'].each { |file| require file } Dir[File.dirname(__FILE__) + '/models/*.rb'].each { |file| require file }
Dir[File.dirname(__FILE__) + '/presenters/*.rb'].each { |file| require file } Dir[File.dirname(__FILE__) + '/presenters/*.rb'].each { |file| require file }
Elasticsearch::Model.client = Elasticsearch::Client.new(host: CommentService.config[:elasticsearch_server], log: false)
$check_index_mapping_exists = defined?(RAKE_SEARCH_INITIALIZE) === nil || RAKE_SEARCH_INITIALIZE === false $check_index_mapping_exists = defined?(RAKE_SEARCH_INITIALIZE) === nil || RAKE_SEARCH_INITIALIZE === false
if $check_index_mapping_exists if $check_index_mapping_exists
......
require 'spec_helper' require 'spec_helper'
require 'unicode_shared_examples' require 'unicode_shared_examples'
describe "app" do describe 'app' do
describe "search" do describe 'search' do
include_context 'search_enabled' include_context 'search_enabled'
before (:each) { set_api_key_header } before (:each) { set_api_key_header }
let(:author) { create_test_user(42) } let(:author) { create(:user) }
let(:course_id) { "test/course/id" } let(:course_id) { 'test/course/id' }
def get_result_ids(result) def get_result_ids(result)
result["collection"].map { |t| t["id"] } result['collection'].map { |t| t['id'] }
end end
describe "GET /api/v1/search/threads" do describe "GET /api/v1/search/threads" do
def assert_empty_response shared_examples_for 'response for invalid request' do
last_response.should be_ok before (:each) { refresh_es_index }
result = parse(last_response.body) subject { get '/api/v1/search/threads', {course_id: course_id}.merge!(parameters) }
result.should == {}
it { should be_ok }
it { should be_an_empty_response }
end end
it "returns an empty result if text parameter is missing" do context 'without text parameter' do
get "/api/v1/search/threads", course_id: course_id let(:parameters) { {} }
assert_empty_response it_behaves_like 'response for invalid request'
end end
it "returns an empty result if sort key is invalid" do context 'with an invalid sort key' do
get "/api/v1/search/threads", course_id: course_id, text: "foobar", sort_key: "invalid" let(:parameters) { {text: 'foobar', sort_key: 'invalid'} }
assert_empty_response it_behaves_like 'response for invalid request'
end end
describe "search for updated/deleted comment/thread works" do describe "search for updated/deleted comment/thread works" do
...@@ -91,6 +93,7 @@ describe "app" do ...@@ -91,6 +93,7 @@ describe "app" do
end end
end end
describe "filtering works" do describe "filtering works" do
let!(:threads) do let!(:threads) do
threads = (0..34).map do |i| threads = (0..34).map do |i|
...@@ -375,21 +378,21 @@ describe "app" do ...@@ -375,21 +378,21 @@ describe "app" do
def test_unicode_data(text) def test_unicode_data(text)
# Elasticsearch may not be able to handle searching for non-ASCII text, # Elasticsearch may not be able to handle searching for non-ASCII text,
# so prepend the text with an ASCII term we can search for. # so prepend the text with an ASCII term we can search for.
search_term = "artichoke" search_term = 'artichoke'
course_id = "unicode/course"
thread = make_thread(author, "#{search_term} #{text}", course_id, "unicode_commentable") thread = create(:comment_thread, body: "#{search_term} #{text}")
make_comment(author, thread, text) create(:comment, comment_thread: thread, body: text)
# Elasticsearch does not necessarily make newly indexed content
# available immediately, so we must explicitly refresh the index
refresh_es_index refresh_es_index
get "/api/v1/search/threads", course_id: course_id, text: search_term
last_response.should be_ok get '/api/v1/search/threads', course_id: thread.course_id, text: search_term
result = parse(last_response.body)["collection"]
result.length.should == 1 expect(last_response).to be_ok
check_thread_result_json(nil, thread, result.first) json = parse(last_response.body)['collection']
expect(json.length).to eq 1
check_thread_result_json(nil, thread, json.first)
end end
include_examples "unicode data" include_examples 'unicode data'
end end
end end
end end
...@@ -174,4 +174,17 @@ describe 'app' do ...@@ -174,4 +174,17 @@ describe 'app' do
expect(last_response.body).to include File.expand_path(__FILE__) expect(last_response.body).to include File.expand_path(__FILE__)
end end
end end
describe 'config' do
describe 'Elasticsearch client' do
subject { Elasticsearch::Model.client }
it 'has a host value set to that from application.yaml' do
expected = URI::parse(CommentService.config[:elasticsearch_server])
host = subject.transport.hosts[0]
host[:port] = host[:port].to_i
expect(URI::HTTP.build(host)).to eq expected
end
end
end
end end
...@@ -17,6 +17,7 @@ require 'support/database_cleaner' ...@@ -17,6 +17,7 @@ require 'support/database_cleaner'
require 'support/elasticsearch' require 'support/elasticsearch'
require 'support/factory_girl' require 'support/factory_girl'
require 'support/rake' require 'support/rake'
require 'support/matchers'
require 'webmock/rspec' require 'webmock/rspec'
WebMock.allow_net_connect! WebMock.allow_net_connect!
......
require 'rspec/expectations'
RSpec::Matchers.define :be_an_empty_response do
match do |actual|
actual.body == '{}'
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment