FEATURE: add advanced order to search (#10385)

FEATURE: add advanced order to search (#10385)

Similar to advanced_filter I introduced advanced_order.

I needed a new option because default orders are evaluated after advanced_filter so I couldn’t use it.

Also, that part is a little bit more generic

elsif word =~ /order:\w+/
  @order = word.gsub('order:', '').to_sym
nil

After those changes, I can use them in plugins in this way:

Search.advanced_order(:votes) do |posts|
  posts.reorder("COALESCE((SELECT dvvc.counter FROM discourse_voting_vote_counters dvvc WHERE dvvc.topic_id = subquery.topic_id), 0) DESC")
end
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index f1de573..0ca72b1 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -190,6 +190,22 @@ class Plugin::Instance
     DiscoursePluginRegistry.register_editable_group_custom_field(field, self)
   end
 
+  # Allows to define custom search order. Example usage:
+  #   Search.advanced_order(:chars) do |posts|
+  #     posts.reorder("(SELECT LENGTH(raw) FROM posts WHERE posts.topic_id = subquery.topic_id) DESC")
+  #   end
+  def register_search_advanced_order(trigger, &block)
+    Search.advanced_order(trigger, &block)
+  end
+
+  # Allows to define custom search filters. Example usage:
+  #   Search.advanced_filter(/^min_chars:(\d+)$/) do |posts, match|
+  #     posts.where("(SELECT LENGTH(p2.raw) FROM posts p2 WHERE p2.id = posts.id) >= ?", match.to_i)
+  #   end
+  def register_search_advanced_filter(trigger, &block)
+    Search.advanced_filter(trigger, &block)
+  end
+
   # Request a new size for topic thumbnails
   # Will respect plugin enabled setting is enabled
   # Size should be an array with two elements [max_width, max_height]
diff --git a/lib/search.rb b/lib/search.rb
index 218aaac..d8f2e40 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -277,6 +277,14 @@ class Search
     @results
   end
 
+  def self.advanced_order(trigger, &block)
+    (@advanced_orders ||= {})[trigger] = block
+  end
+
+  def self.advanced_orders
+    @advanced_orders
+  end
+
   def self.advanced_filter(trigger, &block)
     (@advanced_filters ||= {})[trigger] = block
   end
@@ -659,11 +667,11 @@ class Search
         end
       end
 
-      if word == 'order:latest' || word == 'l'
+      if word == 'l'
         @order = :latest
         nil
-      elsif word == 'order:latest_topic'
-        @order = :latest_topic
+      elsif word =~ /order:\w+/
+        @order = word.gsub('order:', '').to_sym
         nil
       elsif word == 'in:title' || word == 't'
         @in_title = true
@@ -677,12 +685,6 @@ class Search
           end
         end
         nil
-      elsif word == 'order:views'
-        @order = :views
-        nil
-      elsif word == 'order:likes'
-        @order = :likes
-        nil
       elsif word == 'in:all'
         @search_all_topics = true
         nil
@@ -1011,6 +1013,11 @@ class Search
       posts = aggregate_relation.from(posts)
     end
 
+    if @order
+      advanced_order = Search.advanced_orders&.fetch(@order, nil)
+      posts = advanced_order.call(posts) if advanced_order
+    end
+
     posts = posts.offset(offset)
     posts.limit(limit)
   end
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 5a7de3b..00ceb74 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -1690,4 +1690,24 @@ describe Search do
     end
   end
 
+  context 'plugin extensions' do
+    let!(:post0) { Fabricate(:post, raw: 'this is the first post about advanced filter with length more than 50 chars') }
+    let!(:post1) { Fabricate(:post, raw: 'this is the second post about advanced filter') }
+
+    it 'allows to define custom filter' do
+      expect(Search.new("advanced").execute.posts).to eq([post1, post0])
+      Search.advanced_filter(/^min_chars:(\d+)$/) do |posts, match|
+        posts.where("(SELECT LENGTH(p2.raw) FROM posts p2 WHERE p2.id = posts.id) >= ?", match.to_i)
+      end
+      expect(Search.new("advanced min_chars:50").execute.posts).to eq([post0])
+    end
+
+    it 'allows to define custom order' do
+      expect(Search.new("advanced").execute.posts).to eq([post1, post0])
+      Search.advanced_order(:chars) do |posts|
+        posts.reorder("(SELECT LENGTH(raw) FROM posts WHERE posts.topic_id = subquery.topic_id) DESC")
+      end
+      expect(Search.new("advanced order:chars").execute.posts).to eq([post0, post1])
+    end
+  end
 end

GitHub sha: 12a00d6d

This commit appears in #10385 which was approved by tgxworld. It was merged by lis2.