Add support to search for all users in directory (#13803)

pull/13851/head
Diego Sampaio 7 years ago committed by GitHub
parent 58584d486f
commit eba8598482
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      app/models/server/models/Users.js
  2. 20
      app/ui/client/views/app/directory.html
  3. 18
      app/ui/client/views/app/directory.js
  4. 4
      packages/rocketchat-i18n/i18n/en.i18n.json
  5. 46
      server/methods/browseChannels.js

@ -18,6 +18,7 @@ export class Users extends Base {
this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 }); this.tryEnsureIndex({ statusConnection: 1 }, { sparse: 1 });
this.tryEnsureIndex({ type: 1 }); this.tryEnsureIndex({ type: 1 });
this.tryEnsureIndex({ 'visitorEmails.address': 1 }); this.tryEnsureIndex({ 'visitorEmails.address': 1 });
this.tryEnsureIndex({ federation: 1 }, { sparse: true });
} }
getLoginTokensByUserId(userId) { getLoginTokensByUserId(userId) {
@ -480,7 +481,7 @@ export class Users extends Base {
return this.find(query, options); return this.find(query, options);
} }
findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields) { findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery = []) {
if (exceptions == null) { exceptions = []; } if (exceptions == null) { exceptions = []; }
if (options == null) { options = {}; } if (options == null) { options = {}; }
if (!_.isArray(exceptions)) { if (!_.isArray(exceptions)) {
@ -504,6 +505,7 @@ export class Users extends Base {
{ {
username: { $exists: true, $nin: exceptions }, username: { $exists: true, $nin: exceptions },
}, },
...extraQuery,
], ],
}; };
@ -511,6 +513,26 @@ export class Users extends Base {
return this._db.find(query, options); return this._db.find(query, options);
} }
findByActiveLocalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localPeer) {
const extraQuery = [
{
$or: [
{ federation: { $exists: false } },
{ 'federation.peer': localPeer },
],
},
];
return this.findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}
findByActiveExternalUsersExcept(searchTerm, exceptions, options, forcedSearchFields, localPeer) {
const extraQuery = [
{ federation: { $exists: true } },
{ 'federation.peer': { $ne: localPeer } },
];
return this.findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}
findUsersByNameOrUsername(nameOrUsername, options) { findUsersByNameOrUsername(nameOrUsername, options) {
const query = { const query = {
username: { username: {

@ -5,21 +5,21 @@
{{>tabs tabs=tabsData}} {{>tabs tabs=tabsData}}
<div class="rc-directory-fields"> <div class="rc-directory-fields">
<div class="rc-input rc-input--small rc-directory-search">
{{> icon icon="magnifier" block="rc-input__icon" }}
<input type="text" class="rc-input__element rc-input__element--small js-search" name="message-search" id="message-search" placeholder={{#if $eq searchType 'channels'}}{{_ "Search_Channels"}}{{/if}}{{#if $eq searchType 'users'}}{{_ "Search_Users"}}{{/if}} autocomplete="off">
</div>
{{#if $eq searchType 'users'}} {{#if $eq searchType 'users'}}
{{#if federationEnabled}} {{#if federationEnabled}}
<label class="rc-select rc-directory-dropdown rc-directory-search"> <label class="rc-select rc-directory-dropdown rc-directory-search">
<select class="rc-select__element js-setting-data js-workspace" name="search-type"> <select class="rc-select__element js-setting-data js-workspace" name="search-type">
<option class="rc-select__option" value="local" selected="true">{{_ "Local_Workspace"}}</option> <option class="rc-select__option" value="local" selected="true">{{_ "Local_Domains"}}</option>
<option class="rc-select__option" value="all">{{_ "Every_Workspace"}}</option> <option class="rc-select__option" value="external">{{_ "External_Domains"}}</option>
</select> </select>
{{> icon block="rc-select__arrow" icon="arrow-down" }} {{> icon block="rc-select__arrow" icon="arrow-down" }}
</label> </label>
{{/if}} {{/if}}
{{/if}} {{/if}}
<div class="rc-input rc-input--small rc-directory-search">
{{> icon icon="magnifier" block="rc-input__icon" }}
<input type="text" class="rc-input__element rc-input__element--small js-search" name="message-search" id="message-search" placeholder={{#if $eq searchType 'channels'}}{{_ "Search_Channels"}}{{/if}}{{#if $eq searchType 'users'}}{{_ "Search_Users"}}{{/if}} autocomplete="off">
</div>
</div> </div>
{{#if $eq searchType 'channels'}} {{#if $eq searchType 'channels'}}
@ -91,10 +91,12 @@
<th class="js-sort {{#if searchSortBy 'username'}}is-sorting{{/if}}" data-sort="username"> <th class="js-sort {{#if searchSortBy 'username'}}is-sorting{{/if}}" data-sort="username">
<div class="table-fake-th"><span>{{_ "Username"}}</span> {{> icon icon=(sortIcon 'username') }}</div> <div class="table-fake-th"><span>{{_ "Username"}}</span> {{> icon icon=(sortIcon 'username') }}</div>
</th> </th>
{{#if canViewOtherUserInfo}}
<th class="js-sort {{#if searchSortBy 'email'}}is-sorting{{/if}}" data-sort="email"> <th class="js-sort {{#if searchSortBy 'email'}}is-sorting{{/if}}" data-sort="email">
<div class="table-fake-th"><span>{{_ "Email"}}</span> {{> icon icon=(sortIcon 'email') }}</div> <div class="table-fake-th"><span>{{_ "Email"}}</span> {{> icon icon=(sortIcon 'email') }}</div>
</th> </th>
{{#if $eq searchWorkspace 'all'}} {{/if}}
{{#if $eq searchWorkspace 'external'}}
<th class="js-sort {{#if searchSortBy 'domain'}}is-sorting{{/if}}" data-sort="domain"> <th class="js-sort {{#if searchSortBy 'domain'}}is-sorting{{/if}}" data-sort="domain">
<div class="table-fake-th"><span>{{_ "Domain"}}</span> {{> icon icon=(sortIcon 'domain') }}</div> <div class="table-fake-th"><span>{{_ "Domain"}}</span> {{> icon icon=(sortIcon 'domain') }}</div>
</th> </th>
@ -118,8 +120,10 @@
</div> </div>
</td> </td>
<td>{{username}}</td> <td>{{username}}</td>
{{#if canViewOtherUserInfo}}
<td>{{email}}</td> <td>{{email}}</td>
{{#if $eq searchWorkspace 'all'}} {{/if}}
{{#if $eq searchWorkspace 'external'}}
<td>{{domain}}</td> <td>{{domain}}</td>
{{/if}} {{/if}}
<td>{{createdAt}}</td> <td>{{createdAt}}</td>

@ -1,6 +1,6 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var'; import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker'; import { hasAllPermission } from '../../../../authorization/client';
import { Template } from 'meteor/templating'; import { Template } from 'meteor/templating';
import _ from 'underscore'; import _ from 'underscore';
import { timeAgo } from './helpers'; import { timeAgo } from './helpers';
@ -187,6 +187,11 @@ Template.directory.helpers({
sortDirection.set('asc'); sortDirection.set('asc');
}; };
}, },
canViewOtherUserInfo() {
const { canViewOtherUserInfo } = Template.instance();
return canViewOtherUserInfo.get();
},
}); });
Template.directory.events({ Template.directory.events({
@ -217,8 +222,7 @@ Template.directory.onRendered(function() {
return this.results.set(result); return this.results.set(result);
} }
Tracker.autorun(() => { this.autorun(() => {
const searchConfig = { const searchConfig = {
text: this.searchText.get(), text: this.searchText.get(),
workspace: this.searchWorkspace.get(), workspace: this.searchWorkspace.get(),
@ -243,7 +247,7 @@ Template.directory.onRendered(function() {
// If there is no result, searching every workspace and // If there is no result, searching every workspace and
// the search text is an email address, try to find a federated user // the search text is an email address, try to find a federated user
if (this.searchWorkspace.get() === 'all' && this.searchText.get().indexOf('@') !== -1) { if (this.searchWorkspace.get() === 'external' && this.searchText.get().indexOf('@') !== -1) {
const email = this.searchText.get(); const email = this.searchText.get();
Meteor.call('federationSearchUsers', email, (error, federatedUsers) => { Meteor.call('federationSearchUsers', email, (error, federatedUsers) => {
@ -297,6 +301,12 @@ Template.directory.onCreated(function() {
this.results = new ReactiveVar([]); this.results = new ReactiveVar([]);
this.isLoading = new ReactiveVar(false); this.isLoading = new ReactiveVar(false);
this.canViewOtherUserInfo = new ReactiveVar(false);
this.autorun(() => {
this.canViewOtherUserInfo.set(hasAllPermission('view-full-other-user-info'));
});
}); });
Template.directory.onRendered(function() { Template.directory.onRendered(function() {

@ -1253,7 +1253,6 @@
"every_hour": "Once every hour", "every_hour": "Once every hour",
"every_six_hours": "Once every six hours", "every_six_hours": "Once every six hours",
"every_day": "Once every day", "every_day": "Once every day",
"Every_Workspace": "Every Workspace",
"Everyone_can_access_this_channel": "Everyone can access this channel", "Everyone_can_access_this_channel": "Everyone can access this channel",
"Example_s": "Example: <code class=\"inline\">%s</code>", "Example_s": "Example: <code class=\"inline\">%s</code>",
"Exclude_Botnames": "Exclude Bots", "Exclude_Botnames": "Exclude Bots",
@ -1265,6 +1264,7 @@
"Export_My_Data": "Export My Data", "Export_My_Data": "Export My Data",
"expression": "Expression", "expression": "Expression",
"Extended": "Extended", "Extended": "Extended",
"External_Domains": "External Domains",
"External_Queue_Service_URL": "External Queue Service URL", "External_Queue_Service_URL": "External Queue Service URL",
"External_Service": "External Service", "External_Service": "External Service",
"Facebook_Page": "Facebook Page", "Facebook_Page": "Facebook Page",
@ -1833,8 +1833,8 @@
"Loading...": "Loading...", "Loading...": "Loading...",
"Loading_more_from_history": "Loading more from history", "Loading_more_from_history": "Loading more from history",
"Loading_suggestion": "Loading suggestions", "Loading_suggestion": "Loading suggestions",
"Local_Domains": "Local Domains",
"Local_Password": "Local Password", "Local_Password": "Local Password",
"Local_Workspace": "Local Workspace",
"Localization": "Localization", "Localization": "Localization",
"Log_Exceptions_to_Channel_Description": "A channel that will receive all captured exceptions. Leave empty to ignore exceptions.", "Log_Exceptions_to_Channel_Description": "A channel that will receive all captured exceptions. Leave empty to ignore exceptions.",
"Log_Exceptions_to_Channel": "Log Exceptions to Channel", "Log_Exceptions_to_Channel": "Log Exceptions to Channel",

@ -52,7 +52,7 @@ Meteor.methods({
limit = limit > 0 ? limit : 10; limit = limit > 0 ? limit : 10;
const options = { const pagination = {
skip, skip,
limit, limit,
}; };
@ -66,7 +66,7 @@ Meteor.methods({
} }
const result = Rooms.findByNameAndType(regex, 'c', { const result = Rooms.findByNameAndType(regex, 'c', {
...options, ...pagination,
sort, sort,
fields: { fields: {
description: 1, description: 1,
@ -90,36 +90,13 @@ Meteor.methods({
return; return;
} }
let exceptions = [user.username]; const exceptions = [user.username];
// Get exceptions
if (type === 'users' && workspace === 'all') {
const nonFederatedUsers = Users.find({
$or: [
{ federation: { $exists: false } },
{ 'federation.peer': Federation.localIdentifier },
],
}, { fields: { username: 1 } }).map((u) => u.username);
exceptions = exceptions.concat(nonFederatedUsers);
} else if (type === 'users' && workspace === 'local') {
const federatedUsers = Users.find({
$and: [
{ federation: { $exists: true } },
{ 'federation.peer': { $ne: Federation.localIdentifier } },
],
}, { fields: { username: 1 } }).map((u) => u.username);
exceptions = exceptions.concat(federatedUsers);
}
const sort = sortUsers(sortBy, sortDirection);
const forcedSearchFields = workspace === 'all' && ['username', 'name', 'emails.address']; const forcedSearchFields = workspace === 'all' && ['username', 'name', 'emails.address'];
const result = Users.findByActiveUsersExcept(text, exceptions, { const options = {
...options, ...pagination,
sort, sort: sortUsers(sortBy, sortDirection),
fields: { fields: {
username: 1, username: 1,
name: 1, name: 1,
@ -127,7 +104,16 @@ Meteor.methods({
emails: 1, emails: 1,
federation: 1, federation: 1,
}, },
}, forcedSearchFields); };
let result;
if (workspace === 'all') {
result = Users.findByActiveUsersExcept(text, exceptions, options, forcedSearchFields);
} else if (workspace === 'external') {
result = Users.findByActiveExternalUsersExcept(text, exceptions, options, forcedSearchFields, Federation.localIdentifier);
} else {
result = Users.findByActiveLocalUsersExcept(text, exceptions, options, forcedSearchFields, Federation.localIdentifier);
}
return { return {
total: result.count(), // count ignores the `skip` and `limit` options total: result.count(), // count ignores the `skip` and `limit` options

Loading…
Cancel
Save