FIX: better filter for groups search (#14262)

FIX: better filter for groups search (#14262)

Follow up of https://github.com/discourse/discourse/pull/14216

Allow plugins to register custom filter with block

diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index f4aa789..783b14e 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -83,9 +83,10 @@ import { replaceTagRenderer } from "discourse/lib/render-tag";
 import { setNewCategoryDefaultColors } from "discourse/routes/new-category";
 import { addSearchResultsCallback } from "discourse/lib/search";
 import { addSearchSuggestion } from "discourse/widgets/search-menu-results";
+import { CUSTOM_USER_SEARCH_OPTIONS } from "select-kit/components/user-chooser";
 
 // If you add any methods to the API ensure you bump up this number
-const PLUGIN_API_VERSION = "0.12.2";
+const PLUGIN_API_VERSION = "0.12.3";
 
 // This helper prevents us from applying the same `modifyClass` over and over in test mode.
 function canModify(klass, type, resolverName, changes) {
@@ -1408,6 +1409,29 @@ class PluginApi {
   }
 
   /**
+   * Add custom user search options.
+   * It is heavily correlated with `register_groups_callback_for_users_search_controller_action` which allows defining custom filter.
+   * Example usage:
+   * `‍``
+   * api.addUserSearchOption("adminsOnly");
+
+   * register_groups_callback_for_users_search_controller_action(:admins_only) do |groups, user|
+   *   groups.where(name: "admins")
+   * end
+   *
+   * {{email-group-user-chooser
+   *   options=(hash
+   *     includeGroups=true
+   *     adminsOnly=true
+   *   )
+   * }}
+   * `‍``
+   */
+  addUserSearchOption(value) {
+    CUSTOM_USER_SEARCH_OPTIONS.push(value);
+  }
+
+  /**
    * Calls a method on a mounted widget whenever an app event happens.
    *
    * For example, if you have a widget with a `key` of `cool-widget` that lives inside the
diff --git a/app/assets/javascripts/discourse/app/lib/user-search.js b/app/assets/javascripts/discourse/app/lib/user-search.js
index c16e74b..6b6900d 100644
--- a/app/assets/javascripts/discourse/app/lib/user-search.js
+++ b/app/assets/javascripts/discourse/app/lib/user-search.js
@@ -20,14 +20,18 @@ export function resetUserSearchCache() {
   oldSearch = null;
 }
 
+export function camelCaseToSnakeCase(text) {
+  return text.replace(/([a-zA-Z])(?=[A-Z])/g, "$1_").toLowerCase();
+}
+
 function performSearch(
   term,
   topicId,
   categoryId,
   includeGroups,
-  customGroupsScope,
   includeMentionableGroups,
   includeMessageableGroups,
+  customUserSearchOptions,
   allowedUsers,
   groupMembersOf,
   includeStagedUsers,
@@ -50,22 +54,29 @@ function performSearch(
     return;
   }
 
+  let data = {
+    term: term,
+    topic_id: topicId,
+    category_id: categoryId,
+    include_groups: includeGroups,
+    include_mentionable_groups: includeMentionableGroups,
+    include_messageable_groups: includeMessageableGroups,
+    groups: groupMembersOf,
+    topic_allowed_users: allowedUsers,
+    include_staged_users: includeStagedUsers,
+    last_seen_users: lastSeenUsers,
+    limit: limit,
+  };
+
+  if (customUserSearchOptions) {
+    Object.keys(customUserSearchOptions).forEach((key) => {
+      data[camelCaseToSnakeCase(key)] = customUserSearchOptions[key];
+    });
+  }
+
   // need to be able to cancel this
   oldSearch = $.ajax(userPath("search/users"), {
-    data: {
-      term: term,
-      topic_id: topicId,
-      category_id: categoryId,
-      include_groups: includeGroups,
-      custom_groups_scope: customGroupsScope,
-      include_mentionable_groups: includeMentionableGroups,
-      include_messageable_groups: includeMessageableGroups,
-      groups: groupMembersOf,
-      topic_allowed_users: allowedUsers,
-      include_staged_users: includeStagedUsers,
-      last_seen_users: lastSeenUsers,
-      limit: limit,
-    },
+    data,
   });
 
   let returnVal = CANCELLED_STATUS;
@@ -102,9 +113,9 @@ let debouncedSearch = function (
   topicId,
   categoryId,
   includeGroups,
-  customGroupsScope,
   includeMentionableGroups,
   includeMessageableGroups,
+  customUserSearchOptions,
   allowedUsers,
   groupMembersOf,
   includeStagedUsers,
@@ -119,9 +130,9 @@ let debouncedSearch = function (
     topicId,
     categoryId,
     includeGroups,
-    customGroupsScope,
     includeMentionableGroups,
     includeMessageableGroups,
+    customUserSearchOptions,
     allowedUsers,
     groupMembersOf,
     includeStagedUsers,
@@ -211,9 +222,9 @@ export default function userSearch(options) {
 
   let term = options.term || "",
     includeGroups = options.includeGroups,
-    customGroupsScope = options.customGroupsScope,
     includeMentionableGroups = options.includeMentionableGroups,
     includeMessageableGroups = options.includeMessageableGroups,
+    customUserSearchOptions = options.customUserSearchOptions,
     allowedUsers = options.allowedUsers,
     topicId = options.topicId,
     categoryId = options.categoryId,
@@ -253,9 +264,9 @@ export default function userSearch(options) {
       topicId,
       categoryId,
       includeGroups,
-      customGroupsScope,
       includeMentionableGroups,
       includeMessageableGroups,
+      customUserSearchOptions,
       allowedUsers,
       groupMembersOf,
       includeStagedUsers,
diff --git a/app/assets/javascripts/select-kit/addon/components/user-chooser.js b/app/assets/javascripts/select-kit/addon/components/user-chooser.js
index 9d3f90c..a364c04 100644
--- a/app/assets/javascripts/select-kit/addon/components/user-chooser.js
+++ b/app/assets/javascripts/select-kit/addon/components/user-chooser.js
@@ -6,6 +6,8 @@ import MultiSelectComponent from "select-kit/components/multi-select";
 import { computed } from "@ember/object";
 import { makeArray } from "discourse-common/lib/helpers";
 
+export const CUSTOM_USER_SEARCH_OPTIONS = [];
+
 export default MultiSelectComponent.extend({
   pluginApiIdentifiers: ["user-chooser"],
   classNames: ["user-chooser"],
@@ -64,19 +66,29 @@ export default MultiSelectComponent.extend({
       return;
     }
 
+    let customUserSearchOptions = CUSTOM_USER_SEARCH_OPTIONS.reduce(
+      (obj, option) => {
+        return {
+          ...obj,
+          [option]: options[option],
+        };
+      },
+      {}
+    );
+
     return userSearch({
       term: filter,
       topicId: options.topicId,
       categoryId: options.categoryId,
       exclude: this.excludedUsers,
       includeGroups: options.includeGroups,
-      customGroupsScope: options.customGroupsScope,
       allowedUsers: options.allowedUsers,
       includeMentionableGroups: options.includeMentionableGroups,
       includeMessageableGroups: options.includeMessageableGroups,
       groupMembersOf: options.groupMembersOf,
       allowEmails: options.allowEmails,
       includeStagedUsers: this.includeStagedUsers,
+      customUserSearchOptions,
     }).then((result) => {
       if (typeof result === "string") {
         // do nothing promise probably got cancelled
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 0835db6..0b1a266 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1123,12 +1123,13 @@ class UsersController < ApplicationController
       end
 
     if groups
-      groups = Group.search_groups(term,
-                                   groups: groups,
-                                   custom_scope: {
-                                     name: params["custom_groups_scope"]&.to_sym,
-                                     arguments: [current_user]
-                                   })
+      DiscoursePluginRegistry.groups_callback_for_users_search_controller_action.each do |param_name, block|
+        if params[param_name.to_s]
+          groups = block.call(groups, current_user)
+        end
+      end
+
+      groups = Group.search_groups(term, groups: groups)
       groups = groups.order('groups.name asc')
 
       to_render[:groups] = groups.map do |m|

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

GitHub sha: e3793e6d7c09646a54885134cb6f95e84121ae08

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