2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-08-17 18:04:11 +08:00

FEATURE: new search order for read topics (#33353)

Introduces order:read and r to search through recently read topics

Also applies to filter route
This commit is contained in:
Sam 2025-06-26 17:02:52 +10:00 committed by GitHub
parent d92de3e4d0
commit e180c62d2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 111 additions and 0 deletions

View file

@ -864,6 +864,17 @@ class Search
else
posts = posts.order("posts.like_count DESC")
end
elsif @order == :read && @guardian.user
posts =
posts.joins(
"JOIN topic_users tu ON tu.topic_id = posts.topic_id AND tu.user_id = #{@guardian.user.id.to_i}",
).where("tu.last_visited_at IS NOT NULL")
if aggregate_search
posts = posts.order("MAX(tu.last_visited_at) DESC")
else
posts = posts.reorder("tu.last_visited_at DESC")
end
elsif allow_relevance_search
posts = sort_by_relevance(posts, type_filter: type_filter, aggregate_search: aggregate_search)
end
@ -938,6 +949,9 @@ class Search
if word == "l"
@order = :latest
nil
elsif word == "r"
@order = :read if @guardian.user
nil
elsif word =~ /\Aorder:\w+\z/i
@order = word.downcase.gsub("order:", "").to_sym
nil

View file

@ -545,6 +545,19 @@ class TopicsFilter
"views" => {
column: "topics.views",
},
"read" => {
column: "tu.last_visited_at",
scope: -> do
if @guardian.user
@scope.joins(
"JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = #{@guardian.user.id.to_i}",
).where("tu.last_visited_at IS NOT NULL")
else
# make sure this works for anon
@scope.joins("LEFT JOIN topic_users tu ON 1 = 0")
end
end,
},
}
private_constant :ORDER_BY_MAPPINGS

View file

@ -3135,4 +3135,35 @@ RSpec.describe Search do
expect(results.posts).to contain_exactly(regular_post)
end
end
it "orders posts by the timestamp of the user's last visit to each topic" do
user = Fabricate(:user)
post2 = nil
freeze_time 2.hours.ago do
post2 = Fabricate(:post, raw: "Read order term")
TopicUser.update_last_read(user, post2.topic.id, post2.post_number, 1, 0)
end
post1 = nil
freeze_time 1.hour.ago do
post1 = Fabricate(:post, raw: "Read order term")
TopicUser.update_last_read(user, post1.topic.id, post1.post_number, 1, 0)
end
_unread_post = Fabricate(:post, raw: "Read order term")
result = Search.execute("Read order term order:read", guardian: Guardian.new(user))
expect(result.posts.map(&:id)).to eq([post1.id, post2.id])
result = Search.execute("Read order term r", guardian: Guardian.new(user))
# also allow for the r shortcul like we have l
expect(result.posts.map(&:id)).to eq([post1.id, post2.id])
result = Search.execute("Read order term r", guardian: Guardian.new)
# no op on anon - all included
expect(result.posts.map(&:id).length).to eq(3)
end
end

View file

@ -1530,6 +1530,59 @@ RSpec.describe TopicsFilter do
include_examples "ordering topics filters", "title", "topic's title"
end
describe "when ordering by user's last visit to topics" do
fab!(:user)
fab!(:topic)
fab!(:topic2) { Fabricate(:topic) }
fab!(:topic3) { Fabricate(:topic) }
before do
freeze_time 3.hours.ago do
TopicUser.update_last_read(user, topic3.id, 1, 1, 0)
end
freeze_time 2.hours.ago do
TopicUser.update_last_read(user, topic.id, 1, 1, 0)
end
freeze_time 1.hour.ago do
TopicUser.update_last_read(user, topic2.id, 1, 1, 0)
end
end
describe "when query string is `order:read`" do
it "should return topics ordered by last visited date in descending order for logged in users" do
expect(
TopicsFilter
.new(guardian: Guardian.new(user))
.filter_from_query_string("order:read")
.pluck(:id),
).to eq([topic2.id, topic.id, topic3.id])
end
it "should not apply any special ordering for anonymous users" do
topics =
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("order:read")
.where(id: [topic.id, topic2.id, topic3.id])
expect(topics.pluck(:id)).to contain_exactly(topic.id, topic2.id, topic3.id)
end
end
describe "when query string is `order:read-asc`" do
it "should return topics ordered by last visited date in ascending order for logged in users" do
expect(
TopicsFilter
.new(guardian: Guardian.new(user))
.filter_from_query_string("order:read-asc")
.pluck(:id),
).to eq([topic3.id, topic.id, topic2.id])
end
end
end
describe "composing multiple order filters" do
fab!(:topic) { Fabricate(:topic, created_at: Time.zone.local(2023, 1, 1), views: 2) }
fab!(:topic2) { Fabricate(:topic, created_at: Time.zone.local(2024, 1, 1), views: 2) }