DEV: Support adding a custom filter on /filter (#27927)

# Context

Currently there is no way to add a custom filter to the experimental `/filter` endpoint. While you can implement a custom `status:` there is no way to include the user's input in a custom query. 

# PR

This PR adds the ability to implement a custom filter. eg. `CUSTOM_FILTER:foo`

- Add `add_filter_custom_filter` for extension
- Add specs
This commit is contained in:
Isaac Janzen 2024-07-17 11:36:38 -05:00 committed by GitHub
parent 6dd09b0868
commit b3e0e920ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 0 deletions

View File

@ -127,6 +127,8 @@ class DiscoursePluginRegistry
define_filtered_register :flag_applies_to_types
define_filtered_register :custom_filter_mappings
def self.register_auth_provider(auth_provider)
self.auth_providers << auth_provider
end

View File

@ -239,6 +239,17 @@ class Plugin::Instance
DiscoursePluginRegistry.register_editable_group_custom_field(field, self)
end
# Allows to define custom filter utilizing the user's input.
# Ensure proper input sanitization before using it in a query.
#
# Example usage:
# add_filter_custom_filter("word_count") do |scope, value|
# scope.where(word_count: value)
# end
def add_filter_custom_filter(name, &block)
DiscoursePluginRegistry.register_custom_filter_mapping({ name => block }, self)
end
# Allows to define custom "status:" filter. Example usage:
# register_custom_filter_by_status("foobar") do |scope|
# scope.where("word_count = 42")

View File

@ -82,6 +82,11 @@ class TopicsFilter
filter_by_number_of_views(min: filter_values)
when "views-max"
filter_by_number_of_views(max: filter_values)
else
if custom_filter =
DiscoursePluginRegistry.custom_filter_mappings.find { |hash| hash.key?(filter) }
@scope = custom_filter[filter].call(@scope, filter_values)
end
end
end

View File

@ -212,6 +212,43 @@ RSpec.describe TopicsFilter do
end
end
describe "when filtering with custom filters" do
fab!(:topic)
fab!(:word_count_topic) { Fabricate(:topic, word_count: 42) }
fab!(:word_count_topic_2) { Fabricate(:topic, word_count: 42) }
let(:word_count_block) { Proc.new { |scope, value| scope.where(word_count: value) } }
let(:id_block) { Proc.new { |scope, value| scope.where(id: value) } }
let(:plugin) { Plugin::Instance.new }
it "supports a custom filter" do
plugin.add_filter_custom_filter("word_count", &word_count_block)
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("word_count:42")
.pluck(:id),
).to contain_exactly(word_count_topic.id, word_count_topic_2.id)
ensure
DiscoursePluginRegistry.reset_register!(:custom_filter_mappings)
end
it "supports multiple custom filters" do
plugin.add_filter_custom_filter("word_count", &word_count_block)
plugin.add_filter_custom_filter("id", &id_block)
expect(
TopicsFilter
.new(guardian: Guardian.new)
.filter_from_query_string("word_count:42 id:#{word_count_topic.id}")
.pluck(:id),
).to contain_exactly(word_count_topic.id)
ensure
DiscoursePluginRegistry.reset_register!(:custom_filter_mappings)
end
end
describe "when filtering by categories" do
fab!(:category) { Fabricate(:category, name: "category") }