FEATURE: advanced search filters for view count

FEATURE: advanced search filters for view count

diff --git a/app/assets/javascripts/discourse/app/components/search-advanced-options.js b/app/assets/javascripts/discourse/app/components/search-advanced-options.js
index 908e391..5b51148 100644
--- a/app/assets/javascripts/discourse/app/components/search-advanced-options.js
+++ b/app/assets/javascripts/discourse/app/components/search-advanced-options.js
@@ -12,6 +12,8 @@ const REGEXP_TAGS_PREFIX = /^(tags?:|#(?=[a-z0-9\-]+::tag))/gi;
 const REGEXP_IN_PREFIX = /^(in|with):/gi;
 const REGEXP_STATUS_PREFIX = /^status:/gi;
 const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/gi;
+const REGEXP_MIN_VIEW_COUNT_PREFIX = /^min_view_count:/gi;
+const REGEXP_MAX_VIEW_COUNT_PREFIX = /^max_view_count:/gi;
 const REGEXP_POST_TIME_PREFIX = /^(before|after):/gi;
 const REGEXP_TAGS_REPLACE = /(^(tags?:|#(?=[a-z0-9\-]+::tag))|::tag\s?$)/gi;
 
@@ -93,6 +95,8 @@ export default Component.extend({
         },
         status: null,
         min_post_count: null,
+        min_view_count: null,
+        max_view_count: null,
         time: {
           when: "before",
           days: null,
@@ -161,6 +165,16 @@ export default Component.extend({
       "searchedTerms.min_post_count",
       REGEXP_MIN_POST_COUNT_PREFIX
     );
+
+    this.setSearchedTermValue(
+      "searchedTerms.min_view_count",
+      REGEXP_MIN_VIEW_COUNT_PREFIX
+    );
+
+    this.setSearchedTermValue(
+      "searchedTerms.max_view_count",
+      REGEXP_MAX_VIEW_COUNT_PREFIX
+    );
   },
 
   findSearchTerms() {
@@ -346,6 +360,18 @@ export default Component.extend({
   },
 
   @action
+  onChangeSearchTermMinViewCount(value) {
+    this.set("searchedTerms.min_view_count", value.length ? value : null);
+    this._updateSearchTermForMinViewCount();
+  },
+
+  @action
+  onChangeSearchTermMaxViewCount(value) {
+    this.set("searchedTerms.max_view_count", value.length ? value : null);
+    this._updateSearchTermForMaxViewCount();
+  },
+
+  @action
   onChangeSearchTermForIn(value) {
     this.set("searchedTerms.in", value);
     this._updateSearchTermForIn();
@@ -627,6 +653,50 @@ export default Component.extend({
     }
   },
 
+  _updateSearchTermForMinViewCount() {
+    const match = this.filterBlocks(REGEXP_MIN_VIEW_COUNT_PREFIX);
+    const viewsCountFilter = this.get("searchedTerms.min_view_count");
+    let searchTerm = this.searchTerm || "";
+
+    if (viewsCountFilter) {
+      if (match.length !== 0) {
+        searchTerm = searchTerm.replace(
+          match[0],
+          `min_view_count:${viewsCountFilter}`
+        );
+      } else {
+        searchTerm += ` min_view_count:${viewsCountFilter}`;
+      }
+
+      this._updateSearchTerm(searchTerm);
+    } else if (match.length !== 0) {
+      searchTerm = searchTerm.replace(match[0], "");
+      this._updateSearchTerm(searchTerm);
+    }
+  },
+
+  _updateSearchTermForMaxViewCount() {
+    const match = this.filterBlocks(REGEXP_MAX_VIEW_COUNT_PREFIX);
+    const viewsCountFilter = this.get("searchedTerms.max_view_count");
+    let searchTerm = this.searchTerm || "";
+
+    if (viewsCountFilter) {
+      if (match.length !== 0) {
+        searchTerm = searchTerm.replace(
+          match[0],
+          `max_view_count:${viewsCountFilter}`
+        );
+      } else {
+        searchTerm += ` max_view_count:${viewsCountFilter}`;
+      }
+
+      this._updateSearchTerm(searchTerm);
+    } else if (match.length !== 0) {
+      searchTerm = searchTerm.replace(match[0], "");
+      this._updateSearchTerm(searchTerm);
+    }
+  },
+
   _updateSearchTerm(searchTerm) {
     this.onChangeSearchTerm(searchTerm.trim());
   },
diff --git a/app/assets/javascripts/discourse/app/templates/components/search-advanced-options.hbs b/app/assets/javascripts/discourse/app/templates/components/search-advanced-options.hbs
index e18885b..c90411d 100644
--- a/app/assets/javascripts/discourse/app/templates/components/search-advanced-options.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/search-advanced-options.hbs
@@ -160,6 +160,33 @@
       }}
     </div>
   </div>
+
+  <div class="control-group pull-left">
+    <label class="control-label" for="search-min-view-count">{{i18n "search.advanced.min_view_count.label"}}</label>
+    <div class="controls">
+      {{input
+        type="number"
+        value=(readonly searchedTerms.min_view_count)
+        class="input-small"
+        id="search-min-view-count"
+        input=(action "onChangeSearchTermMinViewCount" value="target.value")
+      }}
+    </div>
+  </div>
+
+  <div class="control-group pull-left">
+    <label class="control-label" for="search-max-view-count">{{i18n "search.advanced.max_view_count.label"}}</label>
+    <div class="controls">
+      {{input
+        type="number"
+        value=(readonly searchedTerms.max_view_count)
+        class="input-small"
+        id="search-max-view-count"
+        input=(action "onChangeSearchTermMaxViewCount" value="target.value")
+      }}
+    </div>
+  </div>
+
 </div>
 
 {{plugin-outlet name="advanced-search-options-below" args=(hash searchedTerms=searchedTerms onChangeSearchedTermField=onChangeSearchedTermField) tagName=""}}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 9f63c6a..23bf56a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -2124,6 +2124,10 @@ en:
             label: Posted
             before: before
             after: after
+        min_view_count:
+          label: Minimum View Count
+        max_view_count:
+          label: Maximum View Count
 
     hamburger_menu: "go to another topic list or category"
     new_item: "new"
diff --git a/lib/search.rb b/lib/search.rb
index 189e0f4..aab510c 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -637,6 +637,14 @@ class Search
     )", file_extensions: file_extensions)
   end
 
+  advanced_filter(/^min_view_count:(\d+)$/i) do |posts, match|
+    posts.where("topics.views >= ?", match.to_i)
+  end
+
+  advanced_filter(/^max_view_count:(\d+)$/i) do |posts, match|
+    posts.where("topics.views <= ?", match.to_i)
+  end
+
   private
 
   def search_tags(posts, match, positive:)
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 62accfd..116f4d6 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -1393,6 +1393,16 @@ describe Search do
       ])
     end
 
+    it 'can filter by topic views' do
+      topic = Fabricate(:topic, views: 100)
+      topic2 = Fabricate(:topic, views: 200)
+      post = Fabricate(:post, raw: 'Topic', topic: topic)
+      post2 = Fabricate(:post, raw: 'Topic', topic: topic2)
+
+      expect(Search.execute('Topic min_view_count:150').posts.map(&:id)).to eq([post2.id])
+      expect(Search.execute('Topic max_view_count:150').posts.map(&:id)).to eq([post.id])
+    end
+
     it 'can search for terms with dots' do
       post = Fabricate(:post, raw: 'Will.2000 Will.Bob.Bill...')
       expect(Search.execute('bill').posts.map(&:id)).to eq([post.id])

GitHub sha: 0c5cd0d1

1 Like