Add/update thread filters (flagged, unread, unanswered).
This commit is contained in:
Родитель
3021d7163b
Коммит
3f21215e2f
|
@ -1,13 +1,29 @@
|
|||
get "#{APIPREFIX}/threads" do # retrieve threads by course
|
||||
|
||||
threads = Content.where({"_type" => "CommentThread", "course_id" => params["course_id"]})
|
||||
if params[:commentable_ids]
|
||||
threads = threads.in({"commentable_id" => params[:commentable_ids].split(",")})
|
||||
end
|
||||
#if a group id is sent, then process the set of threads with that group id or with no group id
|
||||
threads = Content.where(_type:"CommentThread", course_id: params["course_id"])
|
||||
if params["group_id"]
|
||||
threads = threads.any_of(
|
||||
{:group_id => params[:group_id].to_i},
|
||||
{:group_id.exists => false},
|
||||
{"group_id" => params[:group_id].to_i},
|
||||
{"group_id" => {"$exists" => false}},
|
||||
)
|
||||
end
|
||||
handle_threads_query(threads)
|
||||
|
||||
handle_threads_query(
|
||||
threads,
|
||||
params["user_id"],
|
||||
params["course_id"],
|
||||
value_to_boolean(params["flagged"]),
|
||||
value_to_boolean(params["unread"]),
|
||||
value_to_boolean(params["unanswered"]),
|
||||
params["sort_key"],
|
||||
params["sort_order"],
|
||||
params["page"],
|
||||
params["per_page"]
|
||||
).to_json
|
||||
end
|
||||
|
||||
get "#{APIPREFIX}/threads/:thread_id" do |thread_id|
|
||||
|
|
|
@ -4,14 +4,26 @@ delete "#{APIPREFIX}/:commentable_id/threads" do |commentable_id|
|
|||
end
|
||||
|
||||
get "#{APIPREFIX}/:commentable_id/threads" do |commentable_id|
|
||||
threads = Content.where(_type:"CommentThread", commentable_id: commentable_id)
|
||||
threads = Content.where({"_type" => "CommentThread", "commentable_id" => commentable_id})
|
||||
if params["group_id"]
|
||||
threads = threads.any_of(
|
||||
{:group_id => params[:group_id].to_i},
|
||||
{:group_id.exists => false},
|
||||
{"group_id" => params[:group_id].to_i},
|
||||
{"group_id" => {"$exists" => false}},
|
||||
)
|
||||
end
|
||||
handle_threads_query(threads)
|
||||
|
||||
handle_threads_query(
|
||||
threads,
|
||||
params["user_id"],
|
||||
params["course_id"],
|
||||
value_to_boolean(params["flagged"]),
|
||||
value_to_boolean(params["unread"]),
|
||||
value_to_boolean(params["unanswered"]),
|
||||
params["sort_key"],
|
||||
params["sort_order"],
|
||||
params["page"],
|
||||
params["per_page"]
|
||||
).to_json
|
||||
end
|
||||
|
||||
post "#{APIPREFIX}/:commentable_id/threads" do |commentable_id|
|
||||
|
|
|
@ -3,7 +3,18 @@ get "#{APIPREFIX}/users/:user_id/notifications" do |user_id|
|
|||
end
|
||||
|
||||
get "#{APIPREFIX}/users/:user_id/subscribed_threads" do |user_id|
|
||||
handle_threads_query(user.subscribed_threads)
|
||||
handle_threads_query(
|
||||
user.subscribed_threads.where({"course_id" => params[:course_id]}),
|
||||
params["user_id"],
|
||||
params["course_id"],
|
||||
value_to_boolean(params["flagged"]),
|
||||
value_to_boolean(params["unread"]),
|
||||
value_to_boolean(params["unanswered"]),
|
||||
params["sort_key"],
|
||||
params["sort_order"],
|
||||
params["page"],
|
||||
params["per_page"]
|
||||
).to_json
|
||||
end
|
||||
|
||||
post "#{APIPREFIX}/users/:user_id/subscriptions" do |user_id|
|
||||
|
|
|
@ -2,15 +2,10 @@ 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)
|
||||
|
||||
search_text = local_params["text"]
|
||||
if !search_text || !sort_criteria
|
||||
if !search_text
|
||||
{}.to_json
|
||||
else
|
||||
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
|
||||
|
@ -72,40 +67,26 @@ get "#{APIPREFIX}/search/threads" do
|
|||
corrected_text = nil if thread_ids.empty?
|
||||
end
|
||||
|
||||
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
|
||||
result_obj = handle_threads_query(
|
||||
CommentThread.in({"_id" => thread_ids.to_a}),
|
||||
local_params["user_id"],
|
||||
local_params["course_id"],
|
||||
value_to_boolean(local_params["flagged"]),
|
||||
value_to_boolean(local_params["unread"]),
|
||||
value_to_boolean(local_params["unanswered"]),
|
||||
local_params["sort_key"],
|
||||
local_params["sort_order"],
|
||||
local_params["page"],
|
||||
local_params["per_page"]
|
||||
)
|
||||
if !result_obj.empty?
|
||||
result_obj[:corrected_text] = corrected_text
|
||||
# NOTE this reflects the total results from ES, but does not consider
|
||||
# any post-filtering that might happen (e.g. unread, flagged...) before
|
||||
# results are shown to the user.
|
||||
result_obj[:total_results] = thread_ids.size
|
||||
end
|
||||
total_results = thread_ids.size
|
||||
num_pages = (total_results + per_page - 1) / per_page
|
||||
|
||||
if results.length == 0
|
||||
collection = []
|
||||
else
|
||||
pres_threads = ThreadListPresenter.new(
|
||||
results,
|
||||
local_params[:user_id] ? user : nil,
|
||||
local_params[:course_id] || results.first.course_id
|
||||
)
|
||||
collection = pres_threads.to_hash
|
||||
end
|
||||
|
||||
json_output = nil
|
||||
self.class.trace_execution_scoped(['Custom/get_search_threads/json_serialize']) do
|
||||
json_output = {
|
||||
collection: collection,
|
||||
corrected_text: corrected_text,
|
||||
total_results: total_results,
|
||||
num_pages: num_pages,
|
||||
page: page,
|
||||
}.to_json
|
||||
end
|
||||
json_output
|
||||
result_obj.to_json
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@ api_key: <%= ENV['API_KEY'] || 'PUT_YOUR_API_KEY_HERE' %>
|
|||
elasticsearch_server: <%= ENV['SEARCH_SERVER'] || 'http://localhost:9200' %>
|
||||
max_deep_search_comment_count: 5000
|
||||
default_locale: <%= ENV['SERVICE_LANGUAGE'] || 'en-US' %>
|
||||
manual_pagination_batch_size: <%= ENV['MANUAL_PAGINATION_BATCH_SIZE'] || 500 %>
|
||||
|
|
117
lib/helpers.rb
117
lib/helpers.rb
|
@ -116,72 +116,101 @@ helpers do
|
|||
|
||||
end
|
||||
|
||||
def handle_threads_query(comment_threads)
|
||||
def handle_threads_query(comment_threads, user_id, course_id, filter_flagged, filter_unread, filter_unanswered, sort_key, sort_order, page, per_page)
|
||||
|
||||
if params[:course_id]
|
||||
comment_threads = comment_threads.where(:course_id=>params[:course_id])
|
||||
|
||||
if params[:flagged]
|
||||
self.class.trace_execution_scoped(['Custom/handle_threads_query/find_flagged']) do
|
||||
#get flagged threads and threads containing flagged responses
|
||||
comment_ids = Comment.where(:course_id=>params[:course_id]).
|
||||
where(:abuse_flaggers.ne => [],:abuse_flaggers.exists => true).
|
||||
collect{|c| c.comment_thread_id}.uniq
|
||||
|
||||
thread_ids = comment_threads.where(:abuse_flaggers.ne => [],:abuse_flaggers.exists => true).
|
||||
collect{|c| c.id}
|
||||
|
||||
comment_ids += thread_ids
|
||||
if filter_flagged
|
||||
self.class.trace_execution_scoped(['Custom/handle_threads_query/find_flagged']) do
|
||||
# TODO replace with aggregate query?
|
||||
comment_ids = Comment.where(:course_id => course_id).
|
||||
where(:abuse_flaggers.ne => [], :abuse_flaggers.exists => true).
|
||||
collect{|c| c.comment_thread_id}.uniq
|
||||
|
||||
comment_threads = comment_threads.where(:id.in => comment_ids)
|
||||
end
|
||||
thread_ids = comment_threads.where(:abuse_flaggers.ne => [], :abuse_flaggers.exists => true).
|
||||
collect{|c| c.id}
|
||||
|
||||
comment_threads = comment_threads.in({"_id" => (comment_ids + thread_ids).uniq})
|
||||
end
|
||||
end
|
||||
|
||||
if params[:commentable_ids]
|
||||
comment_threads = comment_threads.in(commentable_id: params[:commentable_ids].split(","))
|
||||
if filter_unanswered
|
||||
self.class.trace_execution_scoped(['Custom/handle_threads_query/find_unanswered']) do
|
||||
endorsed_thread_ids = Comment.where(:course_id => course_id).
|
||||
where(:parent_id.exists => false, :endorsed => true).
|
||||
collect{|c| c.comment_thread_id}.uniq
|
||||
|
||||
comment_threads = comment_threads.where({"thread_type" => :question}).nin({"_id" => endorsed_thread_ids})
|
||||
end
|
||||
end
|
||||
|
||||
sort_criteria = get_sort_criteria(params)
|
||||
|
||||
sort_criteria = get_sort_criteria(sort_key, sort_order)
|
||||
if not sort_criteria
|
||||
{}.to_json
|
||||
{}
|
||||
else
|
||||
page = (params["page"] || DEFAULT_PAGE).to_i
|
||||
per_page = (params["per_page"] || DEFAULT_PER_PAGE).to_i
|
||||
request_user = user_id ? user : nil
|
||||
page = (page || DEFAULT_PAGE).to_i
|
||||
per_page = (per_page || DEFAULT_PER_PAGE).to_i
|
||||
|
||||
comment_threads = comment_threads.order_by(sort_criteria)
|
||||
num_pages = [1, (comment_threads.count / per_page.to_f).ceil].max
|
||||
page = [num_pages, [1, page].max].min
|
||||
# actual query happens here (by doing to_a)
|
||||
threads = comment_threads.page(page).per(per_page).to_a
|
||||
|
||||
if request_user and filter_unread
|
||||
# Filter and paginate based on user read state. Requires joining a subdocument of the
|
||||
# user object with documents in the contents collection, which has to be done in memory.
|
||||
read_dates = {}
|
||||
read_state = request_user.read_states.where(:course_id => course_id).first
|
||||
if read_state
|
||||
read_dates = read_state["last_read_times"].to_hash
|
||||
end
|
||||
|
||||
threads = []
|
||||
skipped = 0
|
||||
to_skip = (page - 1) * per_page
|
||||
has_more = false
|
||||
# batch_size is used to cap the number of documents we might load into memory at any given time
|
||||
# TODO: starting with Mongoid 3.1, you can just do comment_threads.batch_size(size).each()
|
||||
comment_threads.query.batch_size(CommentService.config["manual_pagination_batch_size"].to_i)
|
||||
Mongoid.unit_of_work(disable: :current) do # this is to prevent Mongoid from memoizing every document we look at
|
||||
comment_threads.each do |thread|
|
||||
thread_key = thread._id.to_s
|
||||
if !read_dates.has_key?(thread_key) || read_dates[thread_key] < thread.last_activity_at
|
||||
if skipped >= to_skip
|
||||
if threads.length == per_page
|
||||
has_more = true
|
||||
break
|
||||
end
|
||||
threads << thread
|
||||
else
|
||||
skipped += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# The following trick makes frontend pagers work without recalculating
|
||||
# the number of all unread threads per user on every request (since the number
|
||||
# of threads in a course could be tens or hundreds of thousands). It has the
|
||||
# effect of showing that there's always just one more page of results, until
|
||||
# there definitely are no more pages. This is really only acceptable for pagers
|
||||
# that don't actually reveal the total number of pages to the user onscreen.
|
||||
num_pages = has_more ? page + 1 : page
|
||||
else
|
||||
# let the installed paginator library handle pagination
|
||||
num_pages = [1, (comment_threads.count / per_page.to_f).ceil].max
|
||||
page = [1, page].max
|
||||
threads = comment_threads.page(page).per(per_page).to_a
|
||||
end
|
||||
|
||||
if threads.length == 0
|
||||
collection = []
|
||||
else
|
||||
pres_threads = ThreadListPresenter.new(
|
||||
threads,
|
||||
params[:user_id] ? user : nil,
|
||||
params[:course_id] || threads.first.course_id
|
||||
)
|
||||
pres_threads = ThreadListPresenter.new(threads, request_user, course_id)
|
||||
collection = pres_threads.to_hash
|
||||
end
|
||||
|
||||
json_output = nil
|
||||
self.class.trace_execution_scoped(['Custom/handle_threads_query/json_serialize']) do
|
||||
json_output = {
|
||||
collection: collection,
|
||||
num_pages: num_pages,
|
||||
page: page,
|
||||
}.to_json
|
||||
end
|
||||
json_output
|
||||
{collection: collection, num_pages: num_pages, page: page}
|
||||
end
|
||||
end
|
||||
|
||||
# Given query params, return sort criteria appropriate for passing to the
|
||||
# order_by function of a Mongoid query. Returns nil if params are not valid.
|
||||
def get_sort_criteria(params)
|
||||
def get_sort_criteria(sort_key, sort_order)
|
||||
sort_key_mapper = {
|
||||
"date" => :created_at,
|
||||
"activity" => :last_activity_at,
|
||||
|
|
|
@ -31,7 +31,7 @@ class User
|
|||
end
|
||||
|
||||
def subscribed_threads
|
||||
CommentThread.where(:id.in => subscribed_thread_ids)
|
||||
CommentThread.in({"_id" => subscribed_thread_ids})
|
||||
end
|
||||
|
||||
def to_hash(params={})
|
||||
|
|
|
@ -103,6 +103,59 @@ describe "app" do
|
|||
rs.length.should == 0
|
||||
end
|
||||
end
|
||||
it "filters unread posts" do
|
||||
user = create_test_user(Random.new)
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, user_id: user.id
|
||||
rs.length.should == 10
|
||||
rs2 = thread_result course_id: DFLT_COURSE_ID, user_id: user.id, unread: true
|
||||
rs2.should == rs
|
||||
user.mark_as_read(@threads[rs.first["title"]])
|
||||
rs3 = thread_result course_id: DFLT_COURSE_ID, user_id: user.id, unread: true
|
||||
rs3.should == rs[1..9]
|
||||
rs[1..8].each { |r| user.mark_as_read(@threads[r["title"]]) }
|
||||
rs4 = thread_result course_id: DFLT_COURSE_ID, user_id: user.id, unread: true
|
||||
rs4.should == rs[9, 1]
|
||||
user.mark_as_read(@threads[rs.last["title"]])
|
||||
rs5 = thread_result course_id: DFLT_COURSE_ID, user_id: user.id, unread: true
|
||||
rs5.should == []
|
||||
make_comment(create_test_user(Random.new), @threads[rs.first["title"]], "new activity")
|
||||
rs6 = thread_result course_id: DFLT_COURSE_ID, user_id: user.id, unread: true
|
||||
rs6.length.should == 1
|
||||
rs6.first["title"].should == rs.first["title"]
|
||||
end
|
||||
it "filters unanswered questions" do
|
||||
%w[t9 t7 t5 t3 t1].each do |thread_key|
|
||||
@threads[thread_key].thread_type = :question
|
||||
@threads[thread_key].save!
|
||||
end
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs.length.should == 5
|
||||
@comments["t1 c0"].endorsed = true
|
||||
@comments["t1 c0"].save!
|
||||
rs2 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs2.length.should == 4
|
||||
%w[t9 t7 t5].each do |thread_key|
|
||||
comment = @threads[thread_key].comments.first
|
||||
comment.endorsed = true
|
||||
comment.save!
|
||||
end
|
||||
rs3 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs3.length.should == 1
|
||||
@comments["t3 c0"].endorsed = true
|
||||
@comments["t3 c0"].save!
|
||||
rs3 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs3.length.should == 0
|
||||
end
|
||||
it "ignores endorsed comments that are not question responses" do
|
||||
thread = @threads["t0"]
|
||||
thread.thread_type = :question
|
||||
thread.save!
|
||||
comment = make_comment(create_test_user(Random.new), thread.comments.first, "comment on a response")
|
||||
comment.endorsed = true
|
||||
comment.save!
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs.length.should == 1
|
||||
end
|
||||
it "correctly considers read state" do
|
||||
user = create_test_user(123)
|
||||
[@threads["t1"], @threads["t2"]].each do |t|
|
||||
|
@ -259,8 +312,8 @@ describe "app" do
|
|||
end
|
||||
|
||||
context "pagination" do
|
||||
def thread_result_page (sort_key, sort_order, page, per_page)
|
||||
get "/api/v1/threads", course_id: DFLT_COURSE_ID, sort_key: sort_key, sort_order: sort_order, page: page, per_page: per_page
|
||||
def thread_result_page (sort_key, sort_order, page, per_page, user_id=nil, unread=false)
|
||||
get "/api/v1/threads", course_id: DFLT_COURSE_ID, sort_key: sort_key, sort_order: sort_order, page: page, per_page: per_page, user_id: user_id, unread: unread
|
||||
last_response.should be_ok
|
||||
parse(last_response.body)
|
||||
end
|
||||
|
@ -282,23 +335,59 @@ describe "app" do
|
|||
result["num_pages"].should == 2
|
||||
result["page"].should == 2
|
||||
end
|
||||
|
||||
def test_paged_order (sort_spec, expected_order, filter_spec=[], user_id=nil)
|
||||
# sort spec is a hash with keys: sort_key, sort_dir, per_page
|
||||
# filter spec is an array of filters to set, e.g. "unread", "flagged"
|
||||
# expected order is an array of the expected titles of returned threads, in the expected order
|
||||
actual_order = []
|
||||
per_page = sort_spec['per_page']
|
||||
num_pages = (expected_order.length + per_page - 1) / per_page
|
||||
num_pages.times do |i|
|
||||
page = i + 1
|
||||
result = thread_result_page(
|
||||
sort_spec['sort_key'],
|
||||
sort_spec['sort_dir'],
|
||||
page,
|
||||
per_page,
|
||||
user_id,
|
||||
filter_spec.include?("unread")
|
||||
)
|
||||
result["collection"].length.should == (page * per_page <= expected_order.length ? per_page : expected_order.length % per_page)
|
||||
if filter_spec.include?("unread")
|
||||
# because of the way we handle num_pages for the unread filter, this is a special case.
|
||||
result["num_pages"].should == (page == num_pages ? page : page + 1)
|
||||
else
|
||||
result["num_pages"].should == num_pages
|
||||
end
|
||||
result["page"].should == page
|
||||
actual_order += result["collection"].map {|v| v["title"]}
|
||||
end
|
||||
actual_order.should == expected_order
|
||||
end
|
||||
|
||||
it "orders correctly across pages" do
|
||||
make_comment(@threads["t5"].author, @threads["t5"], "extra comment")
|
||||
@threads["t7"].pinned = true
|
||||
@threads["t7"].save!
|
||||
expected_order = move_to_front(move_to_end(@default_order, "t5"), "t7")
|
||||
actual_order = []
|
||||
per_page = 3
|
||||
num_pages = (@threads.length + per_page - 1) / per_page
|
||||
num_pages.times do |i|
|
||||
page = i + 1
|
||||
result = thread_result_page("comments", "asc", page, per_page)
|
||||
result["collection"].length.should == (page * per_page <= @threads.length ? per_page : @threads.length % per_page)
|
||||
result["num_pages"].should == num_pages
|
||||
result["page"].should == page
|
||||
actual_order += result["collection"].map {|v| v["title"]}
|
||||
end
|
||||
actual_order.should == expected_order
|
||||
test_paged_order({'sort_key'=>'comments', 'sort_dir'=>'asc', 'per_page'=>3}, expected_order)
|
||||
end
|
||||
|
||||
it "orders correctly acrosss pages with unread filter" do
|
||||
user = create_test_user(Random.new)
|
||||
user.mark_as_read(@threads["t0"])
|
||||
user.mark_as_read(@threads["t9"])
|
||||
make_comment(@threads["t5"].author, @threads["t5"], "extra comment")
|
||||
@threads["t7"].pinned = true
|
||||
@threads["t7"].save!
|
||||
expected_order = move_to_front(move_to_end(@default_order[1..8], "t5"), "t7")
|
||||
test_paged_order(
|
||||
{'sort_key'=>'comments', 'sort_dir'=>'asc', 'per_page'=>3},
|
||||
expected_order,
|
||||
["unread"],
|
||||
user.id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe "app" do
|
||||
describe "notifications and subscriptions" do
|
||||
|
||||
let(:subscriber) { create_test_user(42) }
|
||||
|
||||
before(:each) do
|
||||
set_api_key_header
|
||||
setup_10_threads
|
||||
%w[t9 t7 t5 t3 t1].each { |t| subscriber.subscribe(@threads[t]) }
|
||||
end
|
||||
|
||||
describe "GET /api/v1/users/:user_id/subscribed_threads" do
|
||||
|
||||
def thread_result(params)
|
||||
get "/api/v1/users/#{subscriber.id}/subscribed_threads", params
|
||||
last_response.should be_ok
|
||||
parse(last_response.body)["collection"]
|
||||
end
|
||||
|
||||
context "when filtering flagged posts" do
|
||||
it "returns threads that are flagged" do
|
||||
@threads["t1"].abuse_flaggers = [1]
|
||||
@threads["t1"].save!
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, flagged: true
|
||||
rs.length.should == 1
|
||||
check_thread_result_json(nil, @threads["t1"], rs.first)
|
||||
end
|
||||
it "returns threads that have flagged comments" do
|
||||
@comments["t2 c3"].abuse_flaggers = [1] # note: not subscribed
|
||||
@comments["t2 c3"].save!
|
||||
@comments["t3 c3"].abuse_flaggers = [1] # subscribed
|
||||
@comments["t3 c3"].save!
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, flagged: true
|
||||
rs.length.should == 1
|
||||
check_thread_result_json(nil, @threads["t3"], rs.first)
|
||||
end
|
||||
it "returns an empty result when no posts were flagged" do
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, flagged: true
|
||||
rs.length.should == 0
|
||||
end
|
||||
end
|
||||
it "filters unread posts" do
|
||||
rs = thread_result course_id: DFLT_COURSE_ID
|
||||
rs.length.should == 5
|
||||
rs2 = thread_result course_id: DFLT_COURSE_ID, unread: true
|
||||
rs2.should == rs
|
||||
subscriber.mark_as_read(@threads[rs.first["title"]])
|
||||
rs3 = thread_result course_id: DFLT_COURSE_ID, unread: true
|
||||
rs3.should == rs[1..4]
|
||||
rs[1..3].each { |r| subscriber.mark_as_read(@threads[r["title"]]) }
|
||||
rs4 = thread_result course_id: DFLT_COURSE_ID, unread: true
|
||||
rs4.should == rs[4, 1]
|
||||
subscriber.mark_as_read(@threads[rs.last["title"]])
|
||||
rs5 = thread_result course_id: DFLT_COURSE_ID, unread: true
|
||||
rs5.should == []
|
||||
make_comment(create_test_user(Random.new), @threads[rs.first["title"]], "new activity")
|
||||
rs6 = thread_result course_id: DFLT_COURSE_ID, unread: true
|
||||
rs6.length.should == 1
|
||||
rs6.first["title"].should == rs.first["title"]
|
||||
end
|
||||
it "filters unanswered questions" do
|
||||
%w[t9 t7].each do |thread_key|
|
||||
@threads[thread_key].thread_type = :question
|
||||
@threads[thread_key].save!
|
||||
end
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs.length.should == 2
|
||||
@comments["t7 c0"].endorsed = true
|
||||
@comments["t7 c0"].save!
|
||||
rs2 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs2.length.should == 1
|
||||
@comments["t9 c0"].endorsed = true
|
||||
@comments["t9 c0"].save!
|
||||
rs3 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs3.length.should == 0
|
||||
end
|
||||
it "ignores endorsed comments that are not question responses" do
|
||||
thread = @threads["t1"]
|
||||
thread.thread_type = :question
|
||||
thread.save!
|
||||
rs = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs.length.should == 1
|
||||
comment = make_comment(create_test_user(Random.new), thread.comments.first, "comment on a response")
|
||||
comment.endorsed = true
|
||||
comment.save!
|
||||
rs2 = thread_result course_id: DFLT_COURSE_ID, unanswered: true
|
||||
rs2.length.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v1/users/:user_id/subscriptions" do
|
||||
it "subscribe a comment thread" do
|
||||
thread = @threads["t0"]
|
||||
post "/api/v1/users/#{subscriber.external_id}/subscriptions", source_type: "thread", source_id: thread.id
|
||||
last_response.should be_ok
|
||||
thread.subscribers.length.should == 1
|
||||
thread.subscribers[0].should == subscriber
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /api/v1/users/:user_id/subscriptions" do
|
||||
it "unsubscribe a comment thread" do
|
||||
thread = @threads["t2"]
|
||||
subscriber.subscribe(thread)
|
||||
thread.subscribers.length.should == 1
|
||||
thread.subscribers[0].should == subscriber
|
||||
delete "/api/v1/users/#{subscriber.external_id}/subscriptions", source_type: "thread", source_id: thread.id
|
||||
last_response.should be_ok
|
||||
thread.subscribers.length.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -40,10 +40,21 @@ describe "app" do
|
|||
let!(:threads) do
|
||||
threads = (0..29).map do |i|
|
||||
thread = make_thread(author, "text", course_id + (i % 2).to_s, "commentable" + (i % 3).to_s)
|
||||
if i < 2
|
||||
comment = make_comment(author, thread, "objectionable")
|
||||
comment.abuse_flaggers = [1]
|
||||
comment.save!
|
||||
end
|
||||
if i % 5 != 0
|
||||
thread.group_id = i % 5
|
||||
thread.save!
|
||||
end
|
||||
if [0, 2, 4].include? i
|
||||
thread.thread_type = :question
|
||||
thread.save!
|
||||
comment = make_comment(author, thread, "response")
|
||||
comment.save!
|
||||
end
|
||||
thread
|
||||
end
|
||||
refresh_es_index
|
||||
|
@ -63,6 +74,42 @@ describe "app" do
|
|||
assert_response_contains((0..29).find_all {|i| i % 2 == 0})
|
||||
end
|
||||
|
||||
it "with unread filter" do
|
||||
user = create_test_user(Random.new)
|
||||
user.mark_as_read(threads[0])
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", user_id: user.id, unread: true
|
||||
assert_response_contains((1..29).find_all {|i| i % 2 == 0})
|
||||
end
|
||||
|
||||
it "with flagged filter" do
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", flagged: true
|
||||
assert_response_contains([0])
|
||||
end
|
||||
|
||||
it "with unanswered filter" do
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true
|
||||
assert_response_contains([0, 2, 4])
|
||||
comment = threads[2].comments.first
|
||||
comment.endorsed = true
|
||||
comment.save!
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true
|
||||
assert_response_contains([0, 4])
|
||||
end
|
||||
|
||||
it "with unanswered filter and group_id" do
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true
|
||||
assert_response_contains([0, 2, 4])
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true, group_id: 2
|
||||
assert_response_contains([0, 2])
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true, group_id: 4
|
||||
assert_response_contains([0, 4])
|
||||
comment = threads[2].comments.first
|
||||
comment.endorsed = true
|
||||
comment.save!
|
||||
get "/api/v1/search/threads", text: "text", course_id: "test/course/id0", unanswered: true, group_id: 2
|
||||
assert_response_contains([0])
|
||||
end
|
||||
|
||||
it "by commentable_id" do
|
||||
get "/api/v1/search/threads", text: "text", commentable_id: "commentable0"
|
||||
assert_response_contains((0..29).find_all {|i| i % 3 == 0})
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# Commenting out until notifications are used again.
|
||||
#
|
||||
#describe "app" do
|
||||
# describe "subscriptions and notifications" do
|
||||
# before(:each) { init_with_subscriptions }
|
||||
# describe "GET /api/v1/users/:user_id/notifications" do
|
||||
# it "get all notifications on the subscribed comment threads for the user" do
|
||||
# user = User.find("1")
|
||||
# get "/api/v1/users/#{user.external_id}/notifications"
|
||||
# last_response.should be_ok
|
||||
# notifications = parse last_response.body
|
||||
# so_easy = Comment.all.select{|c| c.body == "this problem is so easy"}.first
|
||||
# not_for_me_neither = Comment.all.select{|c| c.body == "not for me neither!"}.first
|
||||
# notification_so_easy = notifications.select{|f| f["notification_type"] == "post_reply" and f["info"]["comment_id"] == so_easy.id.to_s}.first
|
||||
# notification_so_easy.should_not be_nil
|
||||
# notification_not_for_me_neither = notifications.select{|f| f["notification_type"] == "post_reply" and f["info"]["comment_id"] == not_for_me_neither.id.to_s}.first
|
||||
# notification_not_for_me_neither.should_not be_nil
|
||||
# end
|
||||
# it "returns error if user does not exist" do #TODO may change later if have user service
|
||||
# get "/api/v1/users/does_not_exist/notifications"
|
||||
# last_response.status.should == 400
|
||||
# end
|
||||
# it "get all notifications on the subscribed commentable for the user" do
|
||||
# user = User.find("1")
|
||||
# get "/api/v1/users/#{user.external_id}/notifications"
|
||||
# last_response.should be_ok
|
||||
# notifications = parse last_response.body
|
||||
# notifications.select{|f| f["notification_type"] == "post_topic"}.length.should == 1
|
||||
# problem_wrong = notifications.select{|f| f["notification_type"] == "post_topic"}.first
|
||||
# problem_wrong["info"]["thread_title"].should == "This problem is wrong"
|
||||
# end
|
||||
# it "get all notifications on the followed user for the user" do
|
||||
# user = User.find("2")
|
||||
# get "/api/v1/users/#{user.external_id}/notifications"
|
||||
# last_response.should be_ok
|
||||
# notifications = parse last_response.body
|
||||
# notifications.select{|f| f["info"]["thread_title"] =~ /what to say/}.first.should_not be_nil
|
||||
# end
|
||||
# end
|
||||
# describe "POST /api/v1/users/:user_id/subscriptions" do
|
||||
# it "follow user" do
|
||||
# user1 = User.find("1")
|
||||
# user2 = User.find("2")
|
||||
# post "/api/v1/users/#{user1.external_id}/subscriptions", source_type: "user", source_id: user2.external_id
|
||||
# last_response.should be_ok
|
||||
# User.find("2").followers.length.should == 1
|
||||
# User.find("2").followers.should include user1
|
||||
# end
|
||||
# it "does not follow the same user twice" do
|
||||
# user1 = User.find("1")
|
||||
# user2 = User.find("2")
|
||||
# post "/api/v1/users/#{user1.external_id}/subscriptions", source_type: "user", source_id: user2.external_id
|
||||
# post "/api/v1/users/#{user1.external_id}/subscriptions", source_type: "user", source_id: user2.external_id
|
||||
# last_response.should be_ok
|
||||
# User.find("2").followers.length.should == 1
|
||||
# end
|
||||
# it "does not follow oneself" do
|
||||
# user = create_test_user(3)
|
||||
# post "/api/v1/users/#{user.external_id}/subscriptions", source_type: "user", source_id: user.external_id
|
||||
# last_response.status.should == 400
|
||||
# user.reload.followers.length.should == 0
|
||||
# end
|
||||
# it "unfollow user" do
|
||||
# user1 = User.find("1")
|
||||
# user2 = User.find("2")
|
||||
# delete "/api/v1/users/#{user2.external_id}/subscriptions", source_type: "user", source_id: user1.external_id
|
||||
# last_response.should be_ok
|
||||
# User.find("1").followers.length.should == 0
|
||||
# end
|
||||
# it "respond ok when unfollowing user twice" do
|
||||
# user1 = User.find("1")
|
||||
# user2 = User.find("2")
|
||||
# delete "/api/v1/users/#{user2.external_id}/subscriptions", source_type: "user", source_id: user1.external_id
|
||||
# delete "/api/v1/users/#{user2.external_id}/subscriptions", source_type: "user", source_id: user1.external_id
|
||||
# last_response.should be_ok
|
||||
# User.find("1").followers.length.should == 0
|
||||
# end
|
||||
# it "subscribe a commentable" do
|
||||
# user3 = create_test_user(3)
|
||||
# post "/api/v1/users/#{user3.external_id}/subscriptions", source_type: "other", source_id: "question_1"
|
||||
# last_response.should be_ok
|
||||
# Commentable.find("question_1").subscribers.length.should == 3
|
||||
# Commentable.find("question_1").subscribers.should include user3
|
||||
# end
|
||||
# it "unsubscribe a commentable" do
|
||||
# user2 = User.find_by(external_id: "2")
|
||||
# delete "/api/v1/users/#{user2.external_id}/subscriptions", source_type: "other", source_id: "question_1"
|
||||
# last_response.should be_ok
|
||||
# Commentable.find("question_1").subscribers.length.should == 1
|
||||
# Commentable.find("question_1").subscribers.should_not include user2
|
||||
# end
|
||||
# it "subscribe a comment thread" do
|
||||
# user1 = User.find_by(external_id: "1")
|
||||
# thread = CommentThread.where(body: "it is unsolvable").first
|
||||
# post "/api/v1/users/#{user1.external_id}/subscriptions", source_type: "thread", source_id: thread.id
|
||||
# last_response.should be_ok
|
||||
# thread = CommentThread.where(body: "it is unsolvable").first
|
||||
# thread.subscribers.length.should == 2
|
||||
# thread.subscribers.should include user1
|
||||
# end
|
||||
# it "unsubscribe a comment thread" do
|
||||
# user2 = User.find_by(external_id: "2")
|
||||
# thread = CommentThread.where(body: "it is unsolvable").first
|
||||
# delete "/api/v1/users/#{user2.external_id}/subscriptions", source_type: "thread", source_id: thread.id
|
||||
# last_response.should be_ok
|
||||
# thread = CommentThread.where(body: "it is unsolvable").first
|
||||
# thread.subscribers.length.should == 0
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#end
|
Загрузка…
Ссылка в новой задаче