FEATURE: Change very high/low search priority to rank at absolute ends.

Prior to this change, we had weights for very_high, high, low and
very_low. This means there were 4 weights to tweak and what weights to
use for `very_high/high` and `very_low/low` pair was hard to explain.
This change makes it such that `very_high` search priority will always
ensure that the posts are ranked at the top while `very_low` search
priority will ensure that the posts are ranked at the very bottom.
This commit is contained in:
Alan Guo Xiang Tan 2020-12-23 15:14:41 +08:00
parent 082a77df69
commit ebe4896e48
7 changed files with 80 additions and 49 deletions

View File

@ -1470,10 +1470,8 @@ en:
search_query_log_max_size: "Maximum amount of search queries to keep" search_query_log_max_size: "Maximum amount of search queries to keep"
search_query_log_max_retention_days: "Maximum amount of time to keep search queries, in days." search_query_log_max_retention_days: "Maximum amount of time to keep search queries, in days."
search_ignore_accents: "Ignore accents when searching for text." search_ignore_accents: "Ignore accents when searching for text."
category_search_priority_very_low_weight: "Weight applied to ranking for very low category search priority."
category_search_priority_low_weight: "Weight applied to ranking for low category search priority." category_search_priority_low_weight: "Weight applied to ranking for low category search priority."
category_search_priority_high_weight: "Weight applied to ranking for high category search priority." category_search_priority_high_weight: "Weight applied to ranking for high category search priority."
category_search_priority_very_high_weight: "Weight applied to ranking for very high category search priority."
allow_uncategorized_topics: "Allow topics to be created without a category. WARNING: If there are any uncategorized topics, you must recategorize them before turning this off." allow_uncategorized_topics: "Allow topics to be created without a category. WARNING: If there are any uncategorized topics, you must recategorize them before turning this off."
allow_duplicate_topic_titles: "Allow topics with identical, duplicate titles." allow_duplicate_topic_titles: "Allow topics with identical, duplicate titles."
allow_duplicate_topic_titles_category: "Allow topics with identical, duplicate titles if the category is different. allow_duplicate_topic_titles must be false." allow_duplicate_topic_titles_category: "Allow topics with identical, duplicate titles if the category is different. allow_duplicate_topic_titles must be false."
@ -2329,10 +2327,8 @@ en:
invalid_hex_value: "Color values have to be 6-digit hexadecimal codes." invalid_hex_value: "Color values have to be 6-digit hexadecimal codes."
empty_selectable_avatars: "You must first upload at least two selectable avatars before enabling this setting." empty_selectable_avatars: "You must first upload at least two selectable avatars before enabling this setting."
category_search_priority: category_search_priority:
very_low_weight_invalid: "You cannot set the weight to be greater than 'category_search_priority_low_weight'." low_weight_invalid: "You cannot set the weight to be greater or equal to 1."
low_weight_invalid: "You cannot set the weight to be greater or equal to 1 or smaller than 'category_search_priority_very_low_weight'." high_weight_invalid: "You cannot set the weight to be smaller or equal to 1."
high_weight_invalid: "You cannot set the weight to be smaller or equal to 1 or greater than 'category_search_priority_very_high_weight'."
very_high_weight_invalid: "You cannot set the weight to be smaller than 'category_search_priority_high_weight'."
allowed_unicode_usernames: allowed_unicode_usernames:
regex_invalid: "The regular expression is invalid: %{error}" regex_invalid: "The regular expression is invalid: %{error}"
leading_trailing_slash: "The regular expression must not start and end with a slash." leading_trailing_slash: "The regular expression must not start and end with a slash."

View File

@ -1950,10 +1950,6 @@ search:
ro: true ro: true
sk: true sk: true
tr_TR: true tr_TR: true
category_search_priority_very_low_weight:
default: 0.6
hidden: true
validator: "CategorySearchPriorityWeightsValidator"
category_search_priority_low_weight: category_search_priority_low_weight:
default: 0.8 default: 0.8
hidden: true hidden: true
@ -1962,10 +1958,6 @@ search:
default: 1.2 default: 1.2
hidden: true hidden: true
validator: "CategorySearchPriorityWeightsValidator" validator: "CategorySearchPriorityWeightsValidator"
category_search_priority_very_high_weight:
default: 1.4
hidden: true
validator: "CategorySearchPriorityWeightsValidator"
uncategorized: uncategorized:
version_checks: version_checks:

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class DeleteStaleCategorySearchPrioritiesFromSiteSettings < ActiveRecord::Migration[6.0]
def up
execute <<~SQL
DELETE FROM site_settings WHERE name IN ('category_search_priority_very_low_weight', 'category_search_priority_very_high_weight')
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end

View File

@ -1019,17 +1019,25 @@ class Search
) )
SQL SQL
category_search_priority = <<~SQL
(
CASE categories.search_priority
WHEN #{Searchable::PRIORITIES[:very_high]}
THEN 3
WHEN #{Searchable::PRIORITIES[:very_low]}
THEN 1
ELSE 2
END
)
SQL
category_priority_weights = <<~SQL category_priority_weights = <<~SQL
( (
CASE categories.search_priority CASE categories.search_priority
WHEN #{Searchable::PRIORITIES[:very_low]}
THEN #{SiteSetting.category_search_priority_very_low_weight}
WHEN #{Searchable::PRIORITIES[:low]} WHEN #{Searchable::PRIORITIES[:low]}
THEN #{SiteSetting.category_search_priority_low_weight} THEN #{SiteSetting.category_search_priority_low_weight}
WHEN #{Searchable::PRIORITIES[:high]} WHEN #{Searchable::PRIORITIES[:high]}
THEN #{SiteSetting.category_search_priority_high_weight} THEN #{SiteSetting.category_search_priority_high_weight}
WHEN #{Searchable::PRIORITIES[:very_high]}
THEN #{SiteSetting.category_search_priority_very_high_weight}
ELSE ELSE
CASE WHEN topics.closed CASE WHEN topics.closed
THEN 0.9 THEN 0.9
@ -1048,9 +1056,9 @@ class Search
posts = posts =
if aggregate_search if aggregate_search
posts.order("MAX(#{data_ranking}) DESC") posts.order("MAX(#{category_search_priority}) DESC", "MAX(#{data_ranking}) DESC")
else else
posts.order("#{data_ranking} DESC") posts.order("#{category_search_priority} DESC", "#{data_ranking} DESC")
end end
posts = posts.order("topics.bumped_at DESC") posts = posts.order("topics.bumped_at DESC")

View File

@ -9,14 +9,10 @@ class CategorySearchPriorityWeightsValidator
val = val.to_f val = val.to_f
case @name case @name
when "category_search_priority_very_low_weight"
val < SiteSetting.category_search_priority_low_weight
when "category_search_priority_low_weight" when "category_search_priority_low_weight"
val < 1 && val > SiteSetting.category_search_priority_very_low_weight val < 1
when "category_search_priority_high_weight" when "category_search_priority_high_weight"
val > 1 && val < SiteSetting.category_search_priority_very_high_weight val > 1
when "category_search_priority_very_high_weight"
val > SiteSetting.category_search_priority_high_weight
end end
end end

View File

@ -5,24 +5,16 @@ require 'validators/category_search_priority_weights_validator'
RSpec.describe CategorySearchPriorityWeightsValidator do RSpec.describe CategorySearchPriorityWeightsValidator do
it "should validate the results correctly" do it "should validate the results correctly" do
expect do [1, 1.1].each do |value|
SiteSetting.category_search_priority_very_low_weight = 0.9
end.to raise_error(Discourse::InvalidParameters)
[1, 0].each do |value|
expect do expect do
SiteSetting.category_search_priority_low_weight = value SiteSetting.category_search_priority_low_weight = value
end.to raise_error(Discourse::InvalidParameters) end.to raise_error(Discourse::InvalidParameters)
end end
['0.2', 10].each do |value| [1, "0.9"].each do |value|
expect do expect do
SiteSetting.category_search_priority_high_weight = value SiteSetting.category_search_priority_high_weight = value
end.to raise_error(Discourse::InvalidParameters) end.to raise_error(Discourse::InvalidParameters)
end end
expect do
SiteSetting.category_search_priority_very_high_weight = 1.1
end.to raise_error(Discourse::InvalidParameters)
end end
end end

View File

@ -448,66 +448,100 @@ describe SearchController do
end end
context "search priority" do context "search priority" do
fab!(:low_priority_category) do fab!(:very_low_priority_category) do
Fabricate( Fabricate(
:category, :category,
search_priority: Searchable::PRIORITIES[:very_low] search_priority: Searchable::PRIORITIES[:very_low]
) )
end end
fab!(:low_priority_category) do
Fabricate(
:category,
search_priority: Searchable::PRIORITIES[:low]
)
end
fab!(:high_priority_category) do fab!(:high_priority_category) do
Fabricate( Fabricate(
:category, :category,
search_priority: Searchable::PRIORITIES[:high] search_priority: Searchable::PRIORITIES[:high]
) )
end end
fab!(:very_high_priority_category) do fab!(:very_high_priority_category) do
Fabricate( Fabricate(
:category, :category,
search_priority: Searchable::PRIORITIES[:very_high] search_priority: Searchable::PRIORITIES[:very_high]
) )
end end
fab!(:very_low_priority_topic) { Fabricate(:topic, category: very_low_priority_category) }
fab!(:low_priority_topic) { Fabricate(:topic, category: low_priority_category) } fab!(:low_priority_topic) { Fabricate(:topic, category: low_priority_category) }
fab!(:high_priority_topic) { Fabricate(:topic, category: high_priority_category) } fab!(:high_priority_topic) { Fabricate(:topic, category: high_priority_category) }
fab!(:very_high_priority_topic) { Fabricate(:topic, category: very_high_priority_category) } fab!(:very_high_priority_topic) { Fabricate(:topic, category: very_high_priority_category) }
fab!(:very_low_priority_post) do
SearchIndexer.enable
Fabricate(:post, topic: very_low_priority_topic, raw: "This is a very Low Priority Post")
end
fab!(:low_priority_post) do fab!(:low_priority_post) do
SearchIndexer.enable SearchIndexer.enable
Fabricate(:post, topic: low_priority_topic, raw: "This is a Low Priority Post")
Fabricate(:post,
topic: low_priority_topic,
raw: "This is a Low Priority Post",
created_at: 1.day.ago,
)
end end
fab!(:hight_priority_post) do
fab!(:high_priority_post) do
SearchIndexer.enable SearchIndexer.enable
Fabricate(:post, topic: high_priority_topic, raw: "This is a High Priority Post") Fabricate(:post, topic: high_priority_topic, raw: "This is a High Priority Post")
end end
fab!(:old_very_hight_priority_post) do
fab!(:very_high_priority_post) do
SearchIndexer.enable SearchIndexer.enable
Fabricate(:old_post, topic: very_high_priority_topic, raw: "This is a Old but Very High Priority Post")
Fabricate(:post,
topic: very_high_priority_topic,
raw: "This is a Old but Very High Priority Post",
created_at: 2.days.ago,
)
end end
it "sort posts with search priority when search term is empty" do it "sort posts with search priority when search term is empty" do
get "/search.json", params: { q: 'status:open' } get "/search.json", params: { q: 'status:open' }
expect(response.status).to eq(200) expect(response.status).to eq(200)
data = response.parsed_body data = response.parsed_body
post1 = data["posts"].find { |e| e["id"] == old_very_hight_priority_post.id } post1 = data["posts"].find { |e| e["id"] == very_high_priority_post.id }
post2 = data["posts"].find { |e| e["id"] == low_priority_post.id } post2 = data["posts"].find { |e| e["id"] == very_low_priority_post.id }
expect(data["posts"][0]["id"]).to eq(old_very_hight_priority_post.id) expect(data["posts"][0]["id"]).to eq(very_high_priority_post.id)
expect(post1["id"]).to be > post2["id"] expect(post1["id"]).to be > post2["id"]
end end
it "sort posts with search priority when no order query" do it "sort posts with search priority when no order query" do
SiteSetting.category_search_priority_high_weight = 999999
SiteSetting.category_search_priority_low_weight = 0
get "/search.json", params: { q: 'status:open Priority Post' } get "/search.json", params: { q: 'status:open Priority Post' }
expect(response.status).to eq(200) expect(response.status).to eq(200)
data = response.parsed_body data = response.parsed_body
expect(data["posts"][0]["id"]).to eq(old_very_hight_priority_post.id) expect(data["posts"][0]["id"]).to eq(very_high_priority_post.id)
expect(data["posts"][1]["id"]).to eq(hight_priority_post.id) expect(data["posts"][1]["id"]).to eq(high_priority_post.id)
expect(data["posts"][2]["id"]).to eq(low_priority_post.id) expect(data["posts"][2]["id"]).to eq(low_priority_post.id)
expect(data["posts"][3]["id"]).to eq(very_low_priority_post.id)
end end
it "doesn't sort posts with search piority when query with order" do it "doesn't sort posts with search piority when query with order" do
get "/search.json", params: { q: 'status:open order:latest Priority Post' } get "/search.json", params: { q: 'status:open order:latest Priority Post' }
expect(response.status).to eq(200) expect(response.status).to eq(200)
data = response.parsed_body data = response.parsed_body
expect(data["posts"][0]["id"]).to eq(hight_priority_post.id) expect(data["posts"][0]["id"]).to eq(high_priority_post.id)
expect(data["posts"][1]["id"]).to eq(low_priority_post.id) expect(data["posts"][1]["id"]).to eq(very_low_priority_post.id)
expect(data["posts"][2]["id"]).to eq(old_very_hight_priority_post.id) expect(data["posts"][2]["id"]).to eq(low_priority_post.id)
expect(data["posts"][3]["id"]).to eq(very_high_priority_post.id)
end end
end end