DEV: Support filtering by date columns on /filter route (#21233)

This commit adds support for the following date filters:

1. `activity-before:<YYYY-MM-DD>` which filters for topics that have been bumped at or before given date
2. `activity-after:<YYYY-MM-DD>` which filters for topics that have been bumped at or after given date
3. `created-before:<YYYY-MM-DD>` which filters for topics that have been created at or before given date
4. `created-after:<YYYY-MM-DD>` which filters for topics that have been created at or after given date
5. `latest-post-before:<YYYY-MM-DD>` which filters for topics with the
latest post posted at or before given date
6. `latest-post-after:<YYYY-MM-DD>` which filters for topics with the
latest post posted at or after given date

If the filter has an invalid value, i.e string that cannot be converted
into a proper date in the `YYYY-MM-DD` format, the filter will be ignored.

If either of each filter is specify multiple times, only the last
occurrence of each filter will be taken into consideration.
This commit is contained in:
Alan Guo Xiang Tan 2023-04-27 14:43:47 +07:00 committed by GitHub
parent e5ec0b84a9
commit 141555136a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 140 additions and 6 deletions

View File

@ -36,12 +36,24 @@ class TopicsFilter
filter_values = extract_and_validate_value_for(filter, values)
case filter
when "activity-before"
filter_by_activity(before: filter_values)
when "activity-after"
filter_by_activity(after: filter_values)
when "category"
filter_categories(values: key_prefixes.zip(filter_values))
when "created-after"
filter_by_created(after: filter_values)
when "created-before"
filter_by_created(before: filter_values)
when "created-by"
filter_created_by_user(usernames: filter_values.flat_map { |value| value.split(",") })
when "in"
filter_in(values: filter_values)
when "latest-post-after"
filter_by_latest_post(after: filter_values)
when "latest-post-before"
filter_by_latest_post(before: filter_values)
when "likes-min"
filter_by_number_of_likes(min: filter_values)
when "likes-max"
@ -99,8 +111,21 @@ class TopicsFilter
private
YYYY_MM_DD_REGEXP =
/^(?<year>[12][0-9]{3})-(?<month>0?[1-9]|1[0-2])-(?<day>0?[1-9]|[12]\d|3[01])$/
private_constant :YYYY_MM_DD_REGEXP
def extract_and_validate_value_for(filter, values)
case filter
when "activity-before", "activity-after", "created-before", "created-after",
"latest-post-before", "latest-post-after"
value = values.last
if match_data = value.match(YYYY_MM_DD_REGEXP)
Time.zone.parse(
"#{match_data[:year].to_i}-#{match_data[:month].to_i}-#{match_data[:day].to_i}",
)
end
when "likes-min", "likes-max", "likes-op-min", "likes-op-max", "posts-min", "posts-max",
"posters-min", "posters-max", "views-min", "views-max"
value = values.last
@ -117,6 +142,18 @@ class TopicsFilter
end
end
def filter_by_activity(before: nil, after: nil)
filter_by_topic_range(column_name: "topics.bumped_at", min: after, max: before)
end
def filter_by_created(before: nil, after: nil)
filter_by_topic_range(column_name: "topics.created_at", min: after, max: before)
end
def filter_by_latest_post(before: nil, after: nil)
filter_by_topic_range(column_name: "topics.last_posted_at", min: after, max: before)
end
def filter_by_number_of_posts(min: nil, max: nil)
filter_by_topic_range(column_name: "topics.posts_count", min:, max:)
end

View File

@ -847,7 +847,7 @@ RSpec.describe TopicsFilter do
end
end
shared_examples "filtering for topics by range" do |filter|
shared_examples "filtering for topics by counts" do |filter|
describe "when query string is `#{filter}-min:1`" do
it "should only return topics with at least 1 #{filter}" do
expect(
@ -933,7 +933,7 @@ RSpec.describe TopicsFilter do
fab!(:topic_with_2_count) { Fabricate(:topic, like_count: 2) }
fab!(:topic_with_3_count) { Fabricate(:topic, like_count: 3) }
include_examples("filtering for topics by range", "likes")
include_examples("filtering for topics by counts", "likes")
end
describe "when filtering by number of posters in a topic" do
@ -941,7 +941,7 @@ RSpec.describe TopicsFilter do
fab!(:topic_with_2_count) { Fabricate(:topic, participant_count: 2) }
fab!(:topic_with_3_count) { Fabricate(:topic, participant_count: 3) }
include_examples("filtering for topics by range", "posters")
include_examples("filtering for topics by counts", "posters")
end
describe "when filtering by number of posts in a topic" do
@ -949,7 +949,7 @@ RSpec.describe TopicsFilter do
fab!(:topic_with_2_count) { Fabricate(:topic, posts_count: 2) }
fab!(:topic_with_3_count) { Fabricate(:topic, posts_count: 3) }
include_examples("filtering for topics by range", "posts")
include_examples("filtering for topics by counts", "posts")
end
describe "when filtering by number of views in a topic" do
@ -957,7 +957,7 @@ RSpec.describe TopicsFilter do
fab!(:topic_with_2_count) { Fabricate(:topic, views: 2) }
fab!(:topic_with_3_count) { Fabricate(:topic, views: 3) }
include_examples("filtering for topics by range", "views")
include_examples("filtering for topics by counts", "views")
end
describe "when filtering by number of likes in the first post of a topic" do
@ -976,7 +976,104 @@ RSpec.describe TopicsFilter do
post.topic
end
include_examples("filtering for topics by range", "likes-op")
include_examples("filtering for topics by counts", "likes-op")
end
shared_examples "filtering for topics by date column" do |filter, column, description|
fab!(:topic) { Fabricate(:topic, column => Time.zone.local(2022, 1, 1)) }
fab!(:topic2) { Fabricate(:topic, column => Time.zone.local(2023, 5, 12)) }
describe "when query string is `#{filter}-after:invalid-date-test`" do
it "should ignore the filter" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-after:invalid-date-test")
.pluck(:id),
).to contain_exactly(topic.id, topic2.id)
end
end
describe "when query string is `#{filter}-after:2022-01-01`" do
it "should only return topics with #{description} after 2022-01-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-after:2022-01-01")
.pluck(:id),
).to contain_exactly(topic.id, topic2.id)
end
end
describe "when query string is `#{filter}-after:2023-01-1`" do
it "should only return topics with #{description} after 2023-01-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-after:2023-01-1")
.pluck(:id),
).to contain_exactly(topic2.id)
end
end
describe "when query string is `#{filter}-after:2023-6-01`" do
it "should only return topics with #{description} after 2023-06-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-after:2023-6-01")
.pluck(:id),
).to eq([])
end
end
describe "when query string is `#{filter}-before:2023-01-01`" do
it "should only return topics with #{description} before 2023-01-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-before:2023-01-01")
.pluck(:id),
).to contain_exactly(topic.id)
end
end
describe "when query string is `#{filter}-before:2023-1-1`" do
it "should only return topics with #{description} before 2023-01-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-before:2023-1-1")
.pluck(:id),
).to contain_exactly(topic.id)
end
end
describe "when query string is `#{filter}-before:2000-01-01`" do
it "should only return topics with #{description} before 2000-01-01" do
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("#{filter}-before:2000-01-01")
.pluck(:id),
).to eq([])
end
end
end
describe "when filtering by activity of topics" do
include_examples "filtering for topics by date column", "activity", :bumped_at, "bumped date"
end
describe "when filtering by creation date of topics" do
include_examples "filtering for topics by date column", "created", :created_at, "created date"
end
describe "when filtering by last post date of topics" do
include_examples "filtering for topics by date column",
"latest-post",
:last_posted_at,
"last posted date"
end
end
end