mirror of
https://github.com/discourse/discourse.git
synced 2025-04-17 12:59:07 +08:00
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
This commit is contained in:
parent
cddba50570
commit
e3793e6d7c
@ -83,9 +83,10 @@ import { replaceTagRenderer } from "discourse/lib/render-tag";
|
|||||||
import { setNewCategoryDefaultColors } from "discourse/routes/new-category";
|
import { setNewCategoryDefaultColors } from "discourse/routes/new-category";
|
||||||
import { addSearchResultsCallback } from "discourse/lib/search";
|
import { addSearchResultsCallback } from "discourse/lib/search";
|
||||||
import { addSearchSuggestion } from "discourse/widgets/search-menu-results";
|
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
|
// 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.
|
// This helper prevents us from applying the same `modifyClass` over and over in test mode.
|
||||||
function canModify(klass, type, resolverName, changes) {
|
function canModify(klass, type, resolverName, changes) {
|
||||||
@ -1407,6 +1408,29 @@ class PluginApi {
|
|||||||
addSearchSuggestion(value);
|
addSearchSuggestion(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Calls a method on a mounted widget whenever an app event happens.
|
||||||
*
|
*
|
||||||
|
@ -20,14 +20,18 @@ export function resetUserSearchCache() {
|
|||||||
oldSearch = null;
|
oldSearch = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function camelCaseToSnakeCase(text) {
|
||||||
|
return text.replace(/([a-zA-Z])(?=[A-Z])/g, "$1_").toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
function performSearch(
|
function performSearch(
|
||||||
term,
|
term,
|
||||||
topicId,
|
topicId,
|
||||||
categoryId,
|
categoryId,
|
||||||
includeGroups,
|
includeGroups,
|
||||||
customGroupsScope,
|
|
||||||
includeMentionableGroups,
|
includeMentionableGroups,
|
||||||
includeMessageableGroups,
|
includeMessageableGroups,
|
||||||
|
customUserSearchOptions,
|
||||||
allowedUsers,
|
allowedUsers,
|
||||||
groupMembersOf,
|
groupMembersOf,
|
||||||
includeStagedUsers,
|
includeStagedUsers,
|
||||||
@ -50,22 +54,29 @@ function performSearch(
|
|||||||
return;
|
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
|
// need to be able to cancel this
|
||||||
oldSearch = $.ajax(userPath("search/users"), {
|
oldSearch = $.ajax(userPath("search/users"), {
|
||||||
data: {
|
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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let returnVal = CANCELLED_STATUS;
|
let returnVal = CANCELLED_STATUS;
|
||||||
@ -102,9 +113,9 @@ let debouncedSearch = function (
|
|||||||
topicId,
|
topicId,
|
||||||
categoryId,
|
categoryId,
|
||||||
includeGroups,
|
includeGroups,
|
||||||
customGroupsScope,
|
|
||||||
includeMentionableGroups,
|
includeMentionableGroups,
|
||||||
includeMessageableGroups,
|
includeMessageableGroups,
|
||||||
|
customUserSearchOptions,
|
||||||
allowedUsers,
|
allowedUsers,
|
||||||
groupMembersOf,
|
groupMembersOf,
|
||||||
includeStagedUsers,
|
includeStagedUsers,
|
||||||
@ -119,9 +130,9 @@ let debouncedSearch = function (
|
|||||||
topicId,
|
topicId,
|
||||||
categoryId,
|
categoryId,
|
||||||
includeGroups,
|
includeGroups,
|
||||||
customGroupsScope,
|
|
||||||
includeMentionableGroups,
|
includeMentionableGroups,
|
||||||
includeMessageableGroups,
|
includeMessageableGroups,
|
||||||
|
customUserSearchOptions,
|
||||||
allowedUsers,
|
allowedUsers,
|
||||||
groupMembersOf,
|
groupMembersOf,
|
||||||
includeStagedUsers,
|
includeStagedUsers,
|
||||||
@ -211,9 +222,9 @@ export default function userSearch(options) {
|
|||||||
|
|
||||||
let term = options.term || "",
|
let term = options.term || "",
|
||||||
includeGroups = options.includeGroups,
|
includeGroups = options.includeGroups,
|
||||||
customGroupsScope = options.customGroupsScope,
|
|
||||||
includeMentionableGroups = options.includeMentionableGroups,
|
includeMentionableGroups = options.includeMentionableGroups,
|
||||||
includeMessageableGroups = options.includeMessageableGroups,
|
includeMessageableGroups = options.includeMessageableGroups,
|
||||||
|
customUserSearchOptions = options.customUserSearchOptions,
|
||||||
allowedUsers = options.allowedUsers,
|
allowedUsers = options.allowedUsers,
|
||||||
topicId = options.topicId,
|
topicId = options.topicId,
|
||||||
categoryId = options.categoryId,
|
categoryId = options.categoryId,
|
||||||
@ -253,9 +264,9 @@ export default function userSearch(options) {
|
|||||||
topicId,
|
topicId,
|
||||||
categoryId,
|
categoryId,
|
||||||
includeGroups,
|
includeGroups,
|
||||||
customGroupsScope,
|
|
||||||
includeMentionableGroups,
|
includeMentionableGroups,
|
||||||
includeMessageableGroups,
|
includeMessageableGroups,
|
||||||
|
customUserSearchOptions,
|
||||||
allowedUsers,
|
allowedUsers,
|
||||||
groupMembersOf,
|
groupMembersOf,
|
||||||
includeStagedUsers,
|
includeStagedUsers,
|
||||||
|
@ -6,6 +6,8 @@ import MultiSelectComponent from "select-kit/components/multi-select";
|
|||||||
import { computed } from "@ember/object";
|
import { computed } from "@ember/object";
|
||||||
import { makeArray } from "discourse-common/lib/helpers";
|
import { makeArray } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
|
export const CUSTOM_USER_SEARCH_OPTIONS = [];
|
||||||
|
|
||||||
export default MultiSelectComponent.extend({
|
export default MultiSelectComponent.extend({
|
||||||
pluginApiIdentifiers: ["user-chooser"],
|
pluginApiIdentifiers: ["user-chooser"],
|
||||||
classNames: ["user-chooser"],
|
classNames: ["user-chooser"],
|
||||||
@ -64,19 +66,29 @@ export default MultiSelectComponent.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let customUserSearchOptions = CUSTOM_USER_SEARCH_OPTIONS.reduce(
|
||||||
|
(obj, option) => {
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
[option]: options[option],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
return userSearch({
|
return userSearch({
|
||||||
term: filter,
|
term: filter,
|
||||||
topicId: options.topicId,
|
topicId: options.topicId,
|
||||||
categoryId: options.categoryId,
|
categoryId: options.categoryId,
|
||||||
exclude: this.excludedUsers,
|
exclude: this.excludedUsers,
|
||||||
includeGroups: options.includeGroups,
|
includeGroups: options.includeGroups,
|
||||||
customGroupsScope: options.customGroupsScope,
|
|
||||||
allowedUsers: options.allowedUsers,
|
allowedUsers: options.allowedUsers,
|
||||||
includeMentionableGroups: options.includeMentionableGroups,
|
includeMentionableGroups: options.includeMentionableGroups,
|
||||||
includeMessageableGroups: options.includeMessageableGroups,
|
includeMessageableGroups: options.includeMessageableGroups,
|
||||||
groupMembersOf: options.groupMembersOf,
|
groupMembersOf: options.groupMembersOf,
|
||||||
allowEmails: options.allowEmails,
|
allowEmails: options.allowEmails,
|
||||||
includeStagedUsers: this.includeStagedUsers,
|
includeStagedUsers: this.includeStagedUsers,
|
||||||
|
customUserSearchOptions,
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
if (typeof result === "string") {
|
if (typeof result === "string") {
|
||||||
// do nothing promise probably got cancelled
|
// do nothing promise probably got cancelled
|
||||||
|
@ -1123,12 +1123,13 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
if groups
|
if groups
|
||||||
groups = Group.search_groups(term,
|
DiscoursePluginRegistry.groups_callback_for_users_search_controller_action.each do |param_name, block|
|
||||||
groups: groups,
|
if params[param_name.to_s]
|
||||||
custom_scope: {
|
groups = block.call(groups, current_user)
|
||||||
name: params["custom_groups_scope"]&.to_sym,
|
end
|
||||||
arguments: [current_user]
|
end
|
||||||
})
|
|
||||||
|
groups = Group.search_groups(term, groups: groups)
|
||||||
groups = groups.order('groups.name asc')
|
groups = groups.order('groups.name asc')
|
||||||
|
|
||||||
to_render[:groups] = groups.map do |m|
|
to_render[:groups] = groups.map do |m|
|
||||||
|
@ -560,10 +560,6 @@ class Group < ActiveRecord::Base
|
|||||||
def self.search_groups(name, groups: nil, custom_scope: {})
|
def self.search_groups(name, groups: nil, custom_scope: {})
|
||||||
groups ||= Group
|
groups ||= Group
|
||||||
|
|
||||||
if custom_scope.present? && DiscoursePluginRegistry.group_scope_for_search.include?(custom_scope[:name])
|
|
||||||
groups = groups.send(custom_scope[:name], *custom_scope[:arguments])
|
|
||||||
end
|
|
||||||
|
|
||||||
groups.where(
|
groups.where(
|
||||||
"name ILIKE :term_like OR full_name ILIKE :term_like", term_like: "%#{name}%"
|
"name ILIKE :term_like OR full_name ILIKE :term_like", term_like: "%#{name}%"
|
||||||
)
|
)
|
||||||
|
@ -68,6 +68,7 @@ class DiscoursePluginRegistry
|
|||||||
define_register :vendored_core_pretty_text, Set
|
define_register :vendored_core_pretty_text, Set
|
||||||
define_register :seedfu_filter, Set
|
define_register :seedfu_filter, Set
|
||||||
define_register :demon_processes, Set
|
define_register :demon_processes, Set
|
||||||
|
define_register :groups_callback_for_users_search_controller_action, Hash
|
||||||
|
|
||||||
define_filtered_register :staff_user_custom_fields
|
define_filtered_register :staff_user_custom_fields
|
||||||
define_filtered_register :public_user_custom_fields
|
define_filtered_register :public_user_custom_fields
|
||||||
@ -77,7 +78,6 @@ class DiscoursePluginRegistry
|
|||||||
|
|
||||||
define_filtered_register :editable_group_custom_fields
|
define_filtered_register :editable_group_custom_fields
|
||||||
define_filtered_register :group_params
|
define_filtered_register :group_params
|
||||||
define_filtered_register :group_scope_for_search
|
|
||||||
|
|
||||||
define_filtered_register :topic_thumbnail_sizes
|
define_filtered_register :topic_thumbnail_sizes
|
||||||
|
|
||||||
|
@ -375,9 +375,19 @@ class Plugin::Instance
|
|||||||
DiscoursePluginRegistry.register_group_param(param, self)
|
DiscoursePluginRegistry.register_group_param(param, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a custom scopes for search to Group, respecting if the plugin is enabled
|
# Add a custom callback for search to Group
|
||||||
def register_group_scope_for_search(scope_name)
|
# Callback is called in UsersController#search_users
|
||||||
DiscoursePluginRegistry.register_group_scope_for_search(scope_name, self)
|
# Block takes groups and optional current_user
|
||||||
|
# For example:
|
||||||
|
# plugin.register_groups_callback_for_users_search_controller_action(:admins_filter) do |groups, user|
|
||||||
|
# groups.where(name: "admins")
|
||||||
|
# end
|
||||||
|
def register_groups_callback_for_users_search_controller_action(callback, &block)
|
||||||
|
if DiscoursePluginRegistry.groups_callback_for_users_search_controller_action.key?(callback)
|
||||||
|
raise "groups_callback_for_users_search_controller_action callback already registered"
|
||||||
|
end
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.groups_callback_for_users_search_controller_action[callback] = block
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add validation method but check that the plugin is enabled
|
# Add validation method but check that the plugin is enabled
|
||||||
|
@ -935,18 +935,6 @@ describe Group do
|
|||||||
expect(Group.search_groups('sOmEthi')).to eq([group])
|
expect(Group.search_groups('sOmEthi')).to eq([group])
|
||||||
expect(Group.search_groups('test2')).to eq([])
|
expect(Group.search_groups('test2')).to eq([])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows to filter with additional scope' do
|
|
||||||
messageable_group
|
|
||||||
|
|
||||||
expect(Group.search_groups('es', custom_scope: { name: :messageable, arguments: [user] }).sort).to eq([messageable_group, group].sort)
|
|
||||||
|
|
||||||
plugin = Plugin::Instance.new
|
|
||||||
plugin.register_group_scope_for_search(:messageable)
|
|
||||||
expect(Group.search_groups('es', custom_scope: { name: :messageable, arguments: [user] }).sort).to eq([messageable_group].sort)
|
|
||||||
|
|
||||||
DiscoursePluginRegistry.reset!
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#bulk_add' do
|
describe '#bulk_add' do
|
||||||
|
@ -4083,6 +4083,25 @@ describe UsersController do
|
|||||||
.to_not include(private_group.name)
|
.to_not include(private_group.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows plugins to register custom groups filter' do
|
||||||
|
get "/u/search/users.json", params: { include_groups: "true", term: "a" }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
groups = response.parsed_body["groups"]
|
||||||
|
expect(groups.count).to eq(6)
|
||||||
|
|
||||||
|
plugin = Plugin::Instance.new
|
||||||
|
plugin.register_groups_callback_for_users_search_controller_action(:admins_filter) do |original_groups, user|
|
||||||
|
original_groups.where(name: "admins")
|
||||||
|
end
|
||||||
|
get "/u/search/users.json", params: { include_groups: "true", admins_filter: "true", term: "a" }
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
groups = response.parsed_body["groups"]
|
||||||
|
expect(groups).to eq([{ "name" => "admins", "full_name" => nil }])
|
||||||
|
|
||||||
|
DiscoursePluginRegistry.reset!
|
||||||
|
end
|
||||||
|
|
||||||
it "doesn't search for groups" do
|
it "doesn't search for groups" do
|
||||||
get "/u/search/users.json", params: {
|
get "/u/search/users.json", params: {
|
||||||
include_mentionable_groups: 'false',
|
include_mentionable_groups: 'false',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user