FEATURE: Allow categories to be prioritized/deprioritized in search. (#7209)

FEATURE: Allow categories to be prioritized/deprioritized in search. (#7209)

diff --git a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6 b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
index 82af15b..b8a63ee 100644
--- a/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-settings.js.es6
@@ -69,7 +69,7 @@ export default buildCategoryPanel("settings", {
       });
     }
 
-    return options.sort((a, b) => a.value <= b.value);
+    return options;
   },
 
   @computed
diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb
index e655a2e..09fc8f2 100644
--- a/app/models/concerns/searchable.rb
+++ b/app/models/concerns/searchable.rb
@@ -2,8 +2,12 @@ module Searchable
   extend ActiveSupport::Concern
 
   PRIORITIES = Enum.new(
+    ignore: 1,
+    very_low: 2,
+    low: 3,
     normal: 0,
-    ignore: 1
+    high: 4,
+    very_high: 5
   )
 
   included do
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index be3e5c7..3c392e4 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2459,6 +2459,10 @@ en:
         options:
           normal: "Normal"
           ignore: "Ignore"
+          very_low: "Very Low"
+          low: "Low"
+          high: "High"
+          very_high: "Very High"
       sort_options:
         default: "default"
         likes: "Likes"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index aa40d87..37f28c1 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1291,6 +1291,10 @@ en:
     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_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_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_duplicate_topic_titles: "Allow topics with identical, duplicate titles."
     unique_posts_mins: "How many minutes before a user can make a post with the same content again"
@@ -2030,6 +2034,11 @@ en:
       max_username_length_exists: "You cannot set the maximum username length below the longest username (%{username})."
       max_username_length_range: "You cannot set the maximum below the minimum."
       invalid_hex_value: "Color values have to be 6-digit hexadecimal codes."
+      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 or smaller than 'category_search_priority_very_low_weight'."
+        high_weight_invalid: "You cannot set the weight to be greater 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'."
 
     placeholder:
       sso_provider_secrets:
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 2a4c905..6dcb7eb 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1614,6 +1614,22 @@ search:
       ro: true
       sk: true
       tr_TR: true
+  category_search_priority_very_low_weight:
+    default: 0.6
+    hidden: true
+    validator: "CategorySearchPriorityWeightsValidator"
+  category_search_priority_low_weight:
+    default: 0.8
+    hidden: true
+    validator: "CategorySearchPriorityWeightsValidator"
+  category_search_priority_high_weight:
+    default: 1.2
+    hidden: true
+    validator: "CategorySearchPriorityWeightsValidator"
+  category_search_priority_very_high_weight:
+    default: 1.4
+    hidden: true
+    validator: "CategorySearchPriorityWeightsValidator"
 
 uncategorized:
   version_checks:
diff --git a/lib/search.rb b/lib/search.rb
index 33ae811..2721f66 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -836,9 +836,27 @@ class Search
         posts = posts.order("posts.like_count DESC")
       end
     else
+      # 0|32 default normalization scaled into the range zero to one
       data_ranking = <<~SQL
-      TS_RANK_CD(
-        post_search_data.search_data, #{ts_query(weight_filter: weights)}
+      (
+        TS_RANK_CD(
+          post_search_data.search_data,
+          #{ts_query(weight_filter: weights)},
+          0|32
+        ) *
+        (
+          CASE categories.search_priority
+          WHEN #{Searchable::PRIORITIES[:very_low]}
+          THEN #{SiteSetting.category_search_priority_very_low_weight}
+          WHEN #{Searchable::PRIORITIES[:low]}
+          THEN #{SiteSetting.category_search_priority_low_weight}
+          WHEN #{Searchable::PRIORITIES[:high]}
+          THEN #{SiteSetting.category_search_priority_high_weight}
+          WHEN #{Searchable::PRIORITIES[:very_high]}
+          THEN #{SiteSetting.category_search_priority_very_high_weight}
+          ELSE 1
+          END
+        )
       )
       SQL
 
diff --git a/lib/site_settings/type_supervisor.rb b/lib/site_settings/type_supervisor.rb
index d354946..2137561 100644
--- a/lib/site_settings/type_supervisor.rb
+++ b/lib/site_settings/type_supervisor.rb
@@ -103,7 +103,9 @@ class SiteSettings::TypeSupervisor
 
     opts[:validator] = opts[:validator].try(:constantize)
     if (validator_type = (opts[:validator] || validator_for(@types[name])))
-      @validators[name] = { class: validator_type, opts: opts.slice(*VALIDATOR_OPTS) }
+      validator_opts = opts.slice(*VALIDATOR_OPTS)
+      validator_opts[:name] = name
+      @validators[name] = { class: validator_type, opts: validator_opts }
     end
   end
 
diff --git a/lib/validators/category_search_priority_weights_validator.rb b/lib/validators/category_search_priority_weights_validator.rb
new file mode 100644
index 0000000..14ea809
--- /dev/null
+++ b/lib/validators/category_search_priority_weights_validator.rb
@@ -0,0 +1,25 @@
+class CategorySearchPriorityWeightsValidator
+  def initialize(opts = {})
+    @name = opts[:name].to_s
+  end
+
+  def valid_value?(val)
+    val = val.to_f
+
+    case @name
+    when "category_search_priority_very_low_weight"
+      val < SiteSetting.category_search_priority_low_weight
+    when "category_search_priority_low_weight"
+      val < 1 && val > SiteSetting.category_search_priority_very_low_weight
+    when "category_search_priority_high_weight"
+      val > 1 && val < SiteSetting.category_search_priority_very_high_weight
+    when "category_search_priority_very_high_weight"
+      val > SiteSetting.category_search_priority_high_weight
+    end
+  end
+
+  def error_message
+    key = @name[/category_search_priority_(\w+)_weight/, 1]
+    I18n.t("site_settings.errors.category_search_priority.#{key}_weight_invalid")
+  end
+end
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 0116e74..4847765 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -486,6 +486,26 @@ describe Search do
       end
     end
 
+    describe 'categories with different priorities' do
+      let(:category2) { Fabricate(:category) }
+
+      it "should return posts in the right order" do
+        raw = "The pure genuine evian"
+        post = Fabricate(:post, topic: category.topic, raw: raw)
+        post2 = Fabricate(:post, topic: category2.topic, raw: raw)
+
+        search = Search.execute(raw)
+
+        expect(search.posts).to eq([post2, post])
+

[... diff too long, it was truncated ...]

GitHub sha: ac661e85

1 Like