DEV: Support posters-(min|max):<count> on /filter route (#21095)

This commit adds support for the `posters-min:<count>` and
`posters-max:<count>` filters for the topics filtering query language.
`posters-min:1` will filter for topics with at least a one poster while
`posters-max:3` will filter for topics with a maximum of 3 posters.

If the filter has an invalid value, i.e string that cannot be converted
into an integer, the filter will be ignored.

If either of each filter is specify multiple times, only the last
occurence of each filter will be taken into consideration.
This commit is contained in:
Alan Guo Xiang Tan 2023-04-14 07:48:38 +08:00 committed by GitHub
parent e190c00bc4
commit 782b26d0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 5 deletions

View File

@ -41,9 +41,21 @@ class TopicsFilter
when "in" when "in"
filter_in(values: values) filter_in(values: values)
when "posts-min" when "posts-min"
filter_by_number_of_posts(min: values.last) min = values.last
break if !integer_string?(min)
filter_by_number_of_posts(min: min)
when "posts-max" when "posts-max"
filter_by_number_of_posts(max: values.last) max = values.last
break if !integer_string?(max)
filter_by_number_of_posts(max: max)
when "posters-min"
min = values.last
break if !integer_string?(min)
filter_by_number_of_posters(min: min)
when "posters-max"
max = values.last
break if !integer_string?(max)
filter_by_number_of_posters(max: max)
when "status" when "status"
values.each { |status| @scope = filter_status(status: status) } values.each { |status| @scope = filter_status(status: status) }
when "tags" when "tags"
@ -81,13 +93,21 @@ class TopicsFilter
private private
def filter_by_number_of_posts(min: nil, max: nil) def filter_by_topic_range(column_name:, min: nil, max: nil)
{ min => ">=", max => "<=" }.each do |value, operator| { min => ">=", max => "<=" }.each do |value, operator|
next if !value || value !~ /^\d+$/ next if !value
@scope = @scope.where("topics.posts_count #{operator} ?", value) @scope = @scope.where("topics.#{column_name} #{operator} ?", value)
end end
end end
def filter_by_number_of_posts(min: nil, max: nil)
filter_by_topic_range(column_name: "posts_count", min:, max:)
end
def filter_by_number_of_posters(min: nil, max: nil)
filter_by_topic_range(column_name: "participant_count", min:, max:)
end
def filter_categories(values:) def filter_categories(values:)
exclude_subcategories_category_slugs = [] exclude_subcategories_category_slugs = []
include_subcategories_category_slugs = [] include_subcategories_category_slugs = []
@ -332,4 +352,8 @@ class TopicsFilter
def include_topics_with_any_tags(tag_ids) def include_topics_with_any_tags(tag_ids)
@scope = @scope.joins(:topic_tags).where("topic_tags.tag_id IN (?)", tag_ids).distinct(:id) @scope = @scope.joins(:topic_tags).where("topic_tags.tag_id IN (?)", tag_ids).distinct(:id)
end end
def integer_string?(string)
string =~ /\A\d+\z/
end
end end

View File

@ -929,5 +929,100 @@ RSpec.describe TopicsFilter do
end end
end end
end end
describe "when filtering by number of posters in a topic" do
fab!(:topic_with_1_participant) { Fabricate(:topic, participant_count: 1) }
fab!(:topic_with_2_participants) { Fabricate(:topic, participant_count: 2) }
fab!(:topic_with_3_participants) { Fabricate(:topic, participant_count: 3) }
describe "when query string is `posters-min:1`" do
it "should only return topics with at least 1 participant" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-min:1")
.pluck(:id),
).to contain_exactly(
topic_with_1_participant.id,
topic_with_2_participants.id,
topic_with_3_participants.id,
)
end
end
describe "when query string is `posters-min:3`" do
it "should only return topics with at least 3 participants" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-min:3")
.pluck(:id),
).to contain_exactly(topic_with_3_participants.id)
end
end
describe "when query string is `posters-max:1`" do
it "should only return topics with at most 1 participant" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-max:1")
.pluck(:id),
).to contain_exactly(topic_with_1_participant.id)
end
end
describe "when query string is `posters-max:3`" do
it "should only return topics with at most 3 participants" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-max:3")
.pluck(:id),
).to contain_exactly(
topic_with_1_participant.id,
topic_with_2_participants.id,
topic_with_3_participants.id,
)
end
end
describe "when query string is `posters-min:1 posters-max:2`" do
it "should only return topics with at least 1 participant and at most 2 participants" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-min:1 posters-max:2")
.pluck(:id),
).to contain_exactly(topic_with_1_participant.id, topic_with_2_participants.id)
end
end
describe "when query string is `posters-min:3 posters-min:2 posters-max:1 posters-max:3`" do
it "should only return topics with at least 2 participants and at most 3 participants as it ignores earlier filters which are duplicated" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-min:3 posters-min:2 posters-max:1 posters-max:3")
.pluck(:id),
).to contain_exactly(topic_with_2_participants.id, topic_with_3_participants.id)
end
end
describe "when query string is `posters-min:invalid posters-max:invalid`" do
it "should ignore the filters with invalid values" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("posters-min:invalid posters-max:invalid")
.pluck(:id),
).to contain_exactly(
topic_with_1_participant.id,
topic_with_2_participants.id,
topic_with_3_participants.id,
)
end
end
end
end end
end end