FEATURE: Advanced search filters for assigned topics (#102)

FEATURE: Advanced search filters for assigned topics (#102)

Adds three new search modifiers:

  • in:assigned for assigned topics
  • in:unassigned for unassigned topics
  • assigned:{username} to list topics assigned to a specific user

These modifiers are all made available in the advanced search sidebar

diff --git a/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.hbs b/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.hbs
new file mode 100644
index 0000000..b3fe0a6
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.hbs
@@ -0,0 +1,12 @@
+<div class="control-group pull-left">
+  <label class="control-label" for="search-assigned-to">{{i18n "search.advanced.assigned.label"}}</label>
+  <div class="controls">
+    {{user-selector
+      excludeCurrentUser=false
+      usernames=searchedTerms.assigned
+      single=true
+      canReceiveUpdates=true
+      class="user-selector-assigned"
+    }}
+  </div>
+</div>
diff --git a/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.js.es6 b/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.js.es6
new file mode 100644
index 0000000..3cdd800
--- /dev/null
+++ b/assets/javascripts/discourse-assign/connectors/advanced-search-options-below/assigned-advanced-search.js.es6
@@ -0,0 +1,5 @@
+export default {
+  shouldRender(args, component) {
+    return component.currentUser && component.currentUser.can_assign;
+  },
+};
diff --git a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6 b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
index 5ce0e70..3956e82 100644
--- a/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
+++ b/assets/javascripts/discourse-assign/initializers/extend-for-assigns.js.es6
@@ -1,12 +1,14 @@
 import { renderAvatar } from "discourse/helpers/user-avatar";
 import { withPluginApi } from "discourse/lib/plugin-api";
 import computed from "discourse-common/utils/decorators";
+import { observes } from "discourse-common/utils/decorators";
 import { iconHTML, iconNode } from "discourse-common/lib/icon-library";
 import { h } from "virtual-dom";
 import { queryRegistry } from "discourse/widgets/widget";
 import { getOwner } from "discourse-common/lib/get-owner";
 import { htmlSafe } from "@ember/template";
 import getURL from "discourse-common/lib/get-url";
+import SearchAdvancedOptions from "discourse/components/search-advanced-options";
 import { addBulkButton } from "discourse/controllers/topic-bulk-actions";
 import TopicButtonAction from "discourse/controllers/topic-bulk-actions";
 import { inject } from "@ember/controller";
@@ -122,6 +124,23 @@ function initialize(api) {
     before: "top",
   });
 
+  api.addAdvancedSearchOptions(
+    api.getCurrentUser() && api.getCurrentUser().can_assign
+      ? {
+          inOptionsForUsers: [
+            {
+              name: I18n.t("search.advanced.in.assigned"),
+              value: "assigned",
+            },
+            {
+              name: I18n.t("search.advanced.in.unassigned"),
+              value: "unassigned",
+            },
+          ],
+        }
+      : {}
+  );
+
   // You can't act on flags claimed by another user
   api.modifyClass(
     "component:flagged-post",
@@ -315,6 +334,8 @@ function initialize(api) {
   api.addKeyboardShortcut("g a", "", { path: "/my/activity/assigned" });
 }
 
+const REGEXP_USERNAME_PREFIX = /^(assigned:)/gi;
+
 export default {
   name: "extend-for-assign",
   initialize(container) {
@@ -322,9 +343,47 @@ export default {
     if (!siteSettings.assign_enabled) {
       return;
     }
-
     const currentUser = container.lookup("current-user:main");
-    if (currentUser.can_assign) {
+    if (currentUser && currentUser.can_assign) {
+      SearchAdvancedOptions.reopen({
+        _init() {
+          this._super();
+
+          this.set("searchedTerms.assigned", "");
+        },
+
+        @observes("searchedTerms.assigned")
+        updateSearchTermForAssignedUsername() {
+          const match = this.filterBlocks(REGEXP_USERNAME_PREFIX);
+          const userFilter = this.get("searchedTerms.assigned");
+          let searchTerm = this.searchTerm || "";
+          let keyword = "assigned";
+          if (userFilter && userFilter.length !== 0) {
+            if (match.length !== 0) {
+              searchTerm = searchTerm.replace(
+                match[0],
+                `${keyword}:${userFilter}`
+              );
+            } else {
+              searchTerm += ` ${keyword}:${userFilter}`;
+            }
+
+            this.set("searchTerm", searchTerm.trim());
+          } else if (match.length !== 0) {
+            searchTerm = searchTerm.replace(match[0], "");
+            this.set("searchTerm", searchTerm.trim());
+          }
+        },
+
+        _update() {
+          this._super(...arguments);
+          this.setSearchedTermValue(
+            "searchedTerms.assigned",
+            REGEXP_USERNAME_PREFIX
+          );
+        },
+      });
+
       TopicButtonAction.reopen({
         assignUser: inject("assign-user"),
         actions: {
@@ -347,7 +406,8 @@ export default {
         class: "btn-default",
       });
     }
-    withPluginApi("0.8.11", (api) => initialize(api, container));
+
+    withPluginApi("0.11.0", (api) => initialize(api, container));
     withPluginApi("0.8.28", (api) =>
       registerTopicFooterButtons(api, container)
     );
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index db73b26..1d0e329 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -53,6 +53,13 @@ en:
         assign_event:
           name: "Assign Event"
           details: "When a user assigns or unassigns a topic."
+    search:
+      advanced:
+        in:
+          assigned: "are assigned"
+          unassigned: "are unassigned"
+        assigned:
+          label: "Assigned to"
     topics:
       bulk:
         unassign: "Unassign Topics"
diff --git a/plugin.rb b/plugin.rb
index 8c7acdf..f13fda2 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -544,4 +544,40 @@ after_initialize do
     end
   end
 
+  register_search_advanced_filter(/in:assigned/) do |posts|
+    if @guardian.can_assign?
+      posts.where("topics.id IN (
+        SELECT tc.topic_id
+        FROM topic_custom_fields tc
+        WHERE tc.name = 'assigned_to_id' AND
+                        tc.value IS NOT NULL
+        )")
+    end
+  end
+
+  register_search_advanced_filter(/in:unassigned/) do |posts|
+    if @guardian.can_assign?
+      posts.where("topics.id NOT IN (
+        SELECT tc.topic_id
+        FROM topic_custom_fields tc
+        WHERE tc.name = 'assigned_to_id' AND
+                        tc.value IS NOT NULL
+        )")
+    end
+  end
+
+  register_search_advanced_filter(/assigned:(.+)$/) do |posts, match|
+    if @guardian.can_assign?
+      if user_id = User.find_by_username(match)&.id
+        posts.where("topics.id IN (
+          SELECT tc.topic_id
+          FROM topic_custom_fields tc
+          WHERE tc.name = 'assigned_to_id' AND
+                          tc.value IS NOT NULL AND
+                          tc.value::int = #{user_id}
+          )")
+      end
+    end
+  end
+
 end
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
new file mode 100644
index 0000000..06eeb50
--- /dev/null
+++ b/spec/components/search_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require_relative '../support/assign_allowed_group'
+
+describe Search do
+
+  fab!(:user) { Fabricate(:active_user) }
+  fab!(:user2) { Fabricate(:user) }
+
+  before do
+    SearchIndexer.enable
+    SiteSetting.assign_enabled = true
+  end
+
+  context 'Advanced search' do
+    include_context 'A group that is allowed to assign'
+
+    let(:post1) { Fabricate(:post) }
+    let(:post2) { Fabricate(:post) }
+    let(:post3) { Fabricate(:post) }
+
+    before do
+      add_to_assign_allowed_group(user)
+      add_to_assign_allowed_group(user2)
+

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

GitHub sha: 303274ea

This commit appears in #102 which was merged by davidtaylorhq.