mirror of
https://github.com/discourse/discourse.git
synced 2025-03-22 11:35:35 +08:00
DEV: Support ordering filters on /filter
route (#21275)
This commit adds support for the following ordering filters: 1. `order:activity` which orders the topics by `Topic#bumped_at` in descending order 2. `order:activity-asc` which orders the topics by `Topic#bumped_at` in ascending order 3. `order:latest-post` which orders the topics by `Topic#last_posted_at` in descending order 4. `order:latest-post-asc` which orders the topics by `Topic#last_posted_at` in ascending order 5. `order:created` which orders the topics by `Topic#created_at` in descending order 6. `order:created-asc` which orders the topics by `Topic#created_at` in ascending order 7. `order:views` which orders the topics by `Topic#views` in descending order 8. `order:views-asc` which orders the topics by `Topic#views` in ascending order 9. `order:likes` which orders the topics by `Topic#likes` in descending order 10. `order:likes-asc` which orders the topics by `Topic#likes` in ascending order 11. `order:likes-op` which orders the topics by `Post#like_count` of the first post in the topic in descending order 12. `order:likes-op-asc` which orders the topics by `Post#like_count` of the first post in the topic in ascending order 13. `order:posters` which orders the topics by `Topic#participant_count` in descending order 14. `order:posters-asc` which orders the topics by `Topic#participant_count` in ascending order 15. `order:category` which orders the topics by `Category#name` of the topic's category in descending order 16. `order:category-asc` which orders the topics by `Category#name` of the topic's category in ascending order Multiple order filters can be composed together and the order of ordering is applied based on the position of the filter in the query string. For example, `order:views order:created` will order the topics by `Topic#views` in descending order and then order the topics by `Topics#created_at` in descending order.
This commit is contained in:
parent
141555136a
commit
6e5e607072
@ -62,6 +62,8 @@ class TopicsFilter
|
||||
filter_by_number_of_likes_in_first_post(min: filter_values)
|
||||
when "likes-op-max"
|
||||
filter_by_number_of_likes_in_first_post(max: filter_values)
|
||||
when "order"
|
||||
order_by(values: filter_values)
|
||||
when "posts-min"
|
||||
filter_by_number_of_posts(min: filter_values)
|
||||
when "posts-max"
|
||||
@ -171,10 +173,7 @@ class TopicsFilter
|
||||
column_name: "first_posts.like_count",
|
||||
min:,
|
||||
max:,
|
||||
scope:
|
||||
@scope.joins(
|
||||
"INNER JOIN posts AS first_posts ON first_posts.topic_id = topics.id AND first_posts.post_number = 1",
|
||||
),
|
||||
scope: self.joins_first_posts(@scope),
|
||||
)
|
||||
end
|
||||
|
||||
@ -426,4 +425,57 @@ class TopicsFilter
|
||||
def include_topics_with_any_tags(tag_ids)
|
||||
@scope = @scope.joins(:topic_tags).where("topic_tags.tag_id IN (?)", tag_ids).distinct(:id)
|
||||
end
|
||||
|
||||
ORDER_BY_MAPPINGS = {
|
||||
"activity" => {
|
||||
column: "topics.bumped_at",
|
||||
},
|
||||
"category" => {
|
||||
column: "categories.name",
|
||||
scope: -> { @scope.joins(:category) },
|
||||
},
|
||||
"created" => {
|
||||
column: "topics.created_at",
|
||||
},
|
||||
"latest-post" => {
|
||||
column: "topics.last_posted_at",
|
||||
},
|
||||
"likes" => {
|
||||
column: "topics.like_count",
|
||||
},
|
||||
"likes-op" => {
|
||||
column: "first_posts.like_count",
|
||||
scope: -> { joins_first_posts(@scope) },
|
||||
},
|
||||
"posters" => {
|
||||
column: "topics.participant_count",
|
||||
},
|
||||
"views" => {
|
||||
column: "topics.views",
|
||||
},
|
||||
}
|
||||
private_constant :ORDER_BY_MAPPINGS
|
||||
|
||||
ORDER_BY_REGEXP = /^(?<order_by>#{ORDER_BY_MAPPINGS.keys.join("|")})(?<asc>-asc)?$/
|
||||
private_constant :ORDER_BY_REGEXP
|
||||
|
||||
def order_by(values:)
|
||||
values.each do |value|
|
||||
match_data = value.match(ORDER_BY_REGEXP)
|
||||
|
||||
if match_data && column_name = ORDER_BY_MAPPINGS.dig(match_data[:order_by], :column)
|
||||
if scope = ORDER_BY_MAPPINGS.dig(match_data[:order_by], :scope)
|
||||
@scope = instance_exec(&scope)
|
||||
end
|
||||
|
||||
@scope = @scope.order(column_name => match_data[:asc] ? :asc : :desc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def joins_first_posts(scope)
|
||||
scope.joins(
|
||||
"INNER JOIN posts AS first_posts ON first_posts.topic_id = topics.id AND first_posts.post_number = 1",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -1075,5 +1075,152 @@ RSpec.describe TopicsFilter do
|
||||
:last_posted_at,
|
||||
"last posted date"
|
||||
end
|
||||
|
||||
describe "ordering topics filter" do
|
||||
# Requires the fabrication of `topic`, `topic2` and `topic3` such that the order of the topics is `topic2`, `topic1`, `topic3`
|
||||
# when ordered by the given filter in descending order.
|
||||
shared_examples "ordering topics filters" do |order, order_description|
|
||||
describe "when query string is `order:#{order}`" do
|
||||
it "should return topics ordered by #{order_description} in descending order" do
|
||||
expect(
|
||||
TopicsFilter
|
||||
.new(guardian: Guardian.new)
|
||||
.filter_from_query_string("order:#{order}")
|
||||
.pluck(:id),
|
||||
).to eq([topic2.id, topic.id, topic3.id])
|
||||
end
|
||||
end
|
||||
|
||||
describe "when query string is `order:#{order}-asc`" do
|
||||
it "should return topics ordered by #{order_description} in ascending order" do
|
||||
expect(
|
||||
TopicsFilter
|
||||
.new(guardian: Guardian.new)
|
||||
.filter_from_query_string("order:#{order}-asc")
|
||||
.pluck(:id),
|
||||
).to eq([topic3.id, topic.id, topic2.id])
|
||||
end
|
||||
end
|
||||
|
||||
describe "when query string is `order:#{order}-invalid`" do
|
||||
it "should return topics ordered by the default order" do
|
||||
expect(
|
||||
TopicsFilter
|
||||
.new(guardian: Guardian.new)
|
||||
.filter_from_query_string("order:#{order}-invalid")
|
||||
.pluck(:id),
|
||||
).to eq(Topic.all.order(:id).pluck(:id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when ordering topics by creation date" do
|
||||
fab!(:topic) { Fabricate(:topic, created_at: Time.zone.local(2023, 1, 1)) }
|
||||
fab!(:topic2) { Fabricate(:topic, created_at: Time.zone.local(2024, 1, 1)) }
|
||||
fab!(:topic3) { Fabricate(:topic, created_at: Time.zone.local(2022, 1, 1)) }
|
||||
|
||||
include_examples "ordering topics filters", "created", "creation date"
|
||||
end
|
||||
|
||||
describe "when ordering topics by last activity date" do
|
||||
fab!(:topic) { Fabricate(:topic, bumped_at: Time.zone.local(2023, 1, 1)) }
|
||||
fab!(:topic2) { Fabricate(:topic, bumped_at: Time.zone.local(2024, 1, 1)) }
|
||||
fab!(:topic3) { Fabricate(:topic, bumped_at: Time.zone.local(2022, 1, 1)) }
|
||||
|
||||
include_examples "ordering topics filters", "activity", "bumped date"
|
||||
end
|
||||
|
||||
describe "when ordering topics by number of likes in the topic" do
|
||||
fab!(:topic) { Fabricate(:topic, like_count: 2) }
|
||||
fab!(:topic2) { Fabricate(:topic, like_count: 3) }
|
||||
fab!(:topic3) { Fabricate(:topic, like_count: 1) }
|
||||
|
||||
include_examples "ordering topics filters", "likes", "number of likes in the topic"
|
||||
end
|
||||
|
||||
describe "when ordering topics by number of participants in the topic" do
|
||||
fab!(:topic) { Fabricate(:topic, participant_count: 2) }
|
||||
fab!(:topic2) { Fabricate(:topic, participant_count: 3) }
|
||||
fab!(:topic3) { Fabricate(:topic, participant_count: 1) }
|
||||
|
||||
include_examples "ordering topics filters", "posters", "number of participants in the topic"
|
||||
end
|
||||
|
||||
describe "when ordering topics by number of topics views" do
|
||||
fab!(:topic) { Fabricate(:topic, views: 2) }
|
||||
fab!(:topic2) { Fabricate(:topic, views: 3) }
|
||||
fab!(:topic3) { Fabricate(:topic, views: 1) }
|
||||
|
||||
include_examples "ordering topics filters", "views", "number of views"
|
||||
end
|
||||
|
||||
describe "when ordering topics by latest post creation date" do
|
||||
fab!(:topic) { Fabricate(:topic, last_posted_at: Time.zone.local(2023, 1, 1)) }
|
||||
fab!(:topic2) { Fabricate(:topic, last_posted_at: Time.zone.local(2024, 1, 1)) }
|
||||
fab!(:topic3) { Fabricate(:topic, last_posted_at: Time.zone.local(2022, 1, 1)) }
|
||||
|
||||
include_examples "ordering topics filters", "latest-post", "latest post creation date"
|
||||
end
|
||||
|
||||
describe "when ordering topics by number of likes in the first post" do
|
||||
fab!(:topic) do
|
||||
post = Fabricate(:post, like_count: 2)
|
||||
post.topic
|
||||
end
|
||||
|
||||
fab!(:topic2) do
|
||||
post = Fabricate(:post, like_count: 3)
|
||||
post.topic
|
||||
end
|
||||
|
||||
fab!(:topic3) do
|
||||
post = Fabricate(:post, like_count: 1)
|
||||
post.topic
|
||||
end
|
||||
|
||||
include_examples "ordering topics filters", "likes-op", "number of likes in the first post"
|
||||
end
|
||||
|
||||
describe "when ordering by topics's category name" do
|
||||
fab!(:category) { Fabricate(:category, name: "Category 1") }
|
||||
fab!(:category2) { Fabricate(:category, name: "Category 2") }
|
||||
fab!(:category3) { Fabricate(:category, name: "Category 3") }
|
||||
|
||||
fab!(:topic) { Fabricate(:topic, category: category2) }
|
||||
fab!(:topic2) { Fabricate(:topic, category: category3) }
|
||||
fab!(:topic3) { Fabricate(:topic, category: category) }
|
||||
|
||||
include_examples "ordering topics filters", "category", "category name"
|
||||
|
||||
describe "when query string is `order:category` and there are multiple topics in a category" do
|
||||
fab!(:topic4) { Fabricate(:topic, category: category) }
|
||||
fab!(:topic5) { Fabricate(:topic, category: category2) }
|
||||
|
||||
it "should return topics ordered by category name in descending order and then topic id in ascending order" do
|
||||
expect(
|
||||
TopicsFilter
|
||||
.new(guardian: Guardian.new)
|
||||
.filter_from_query_string("order:category")
|
||||
.pluck(:id),
|
||||
).to eq([topic2.id, topic.id, topic5.id, topic3.id, topic4.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when query string is `order:created order:views`" 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) }
|
||||
fab!(:topic3) { Fabricate(:topic, created_at: Time.zone.local(2024, 1, 1), views: 1) }
|
||||
|
||||
it "should return topics ordered by creation date in descending order and then number of views in descending order" do
|
||||
expect(
|
||||
TopicsFilter
|
||||
.new(guardian: Guardian.new)
|
||||
.filter_from_query_string("order:created order:views")
|
||||
.pluck(:id),
|
||||
).to eq([topic2.id, topic3.id, topic.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user