Skip to content

Commit

Permalink
Merge pull request #8 from bhardwajaditya/service-account-discovery
Browse files Browse the repository at this point in the history
[NEW] Service account directory feature
  • Loading branch information
kb0304 authored Jun 27, 2019
2 parents fdac5d2 + bd2811d commit 117d725
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 3 deletions.
68 changes: 68 additions & 0 deletions app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,9 @@ export class Users extends Base {
{
username: { $exists: true, $nin: exceptions },
},
{
u: { $exists: false },
},
...extraQuery,
],
};
Expand All @@ -535,6 +538,9 @@ export class Users extends Base {
{ 'federation.peer': localPeer },
],
},
{
u: { $exists: false },
},
];
return this.findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}
Expand All @@ -543,10 +549,72 @@ export class Users extends Base {
const extraQuery = [
{ federation: { $exists: true } },
{ 'federation.peer': { $ne: localPeer } },
{
u: { $exists: false },
},
];
return this.findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}

findByActiveServiceAccountsExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery = []) {
if (exceptions == null) { exceptions = []; }
if (options == null) { options = {}; }
if (!_.isArray(exceptions)) {
exceptions = [exceptions];
}

const termRegex = new RegExp(s.escapeRegExp(searchTerm), 'i');
const searchFields = forcedSearchFields || settings.get('Service_Accounts_SearchFields').trim().split(',');
const orStmt = _.reduce(searchFields, function(acc, el) {
acc.push({ [el.trim()]: termRegex });
return acc;
}, []);

const query = {
$and: [
{
active: true,
$or: orStmt,
},
{
username: { $exists: true, $nin: exceptions },
},
{
u: { $exists: true },
},
...extraQuery,
],
};

return this._db.find(query, options);
}

findByActiveExternalServiceAccountsExcept(searchTerm, exceptions, options, forcedSearchFields, localPeer) {
const extraQuery = [
{ federation: { $exists: true } },
{ 'federation.peer': { $ne: localPeer } },
{
u: { $exists: true },
},
];
return this.findByActiveServiceAccountsExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}

findByActiveLocalServiceAccountsExcept(searchTerm, exceptions, options, forcedSearchFields, localPeer) {
const extraQuery = [
{
$or: [
{ federation: { $exists: false } },
{ 'federation.peer': localPeer },
],
},
{
u: { $exists: true },
},
];
return this.findByActiveServiceAccountsExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery);
}

findUsersByNameOrUsername(nameOrUsername, options) {
const query = {
username: {
Expand Down
4 changes: 4 additions & 0 deletions app/service-accounts/server/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ Meteor.startup(() => {
type: 'int',
public: true,
});
this.add('Service_Accounts_SearchFields', 'username, name, description', {
type: 'string',
public: true,
});
});
});
61 changes: 60 additions & 1 deletion app/ui/client/views/app/directory.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
class="rc-input__element js-search"
name="message-search"
id="message-search"
placeholder="{{#if $eq searchType 'channels'}}{{_ "Search_Channels"}}{{/if}}{{#if $eq searchType 'users'}}{{_ "Search_Users"}}{{/if}}"
placeholder="{{#if $eq searchType 'channels'}}{{_ "Search_Channels"}}{{/if}}{{#if $eq searchType 'users'}}{{_ "Search_Users"}}{{/if}}{{#if $eq searchType 'serviceAccounts'}}{{_ "Search_ServiceAccounts"}}{{/if}}"
autocomplete="off">
</div>
{{#if $eq searchType 'users'}}
Expand Down Expand Up @@ -164,6 +164,65 @@
</tbody>
{{/table}}
{{/if}}
{{#if $eq searchType 'serviceAccounts'}}
{{#table onItemClick=onTableItemClick onScroll=onTableScroll onResize=onTableResize onSort=onTableSort}}
<thead>
<tr>
<th class="directory__user-name js-sort {{#if searchSortBy 'name'}}is-sorting{{/if}}" data-sort="name">
<div class="table-fake-th"><span>{{_ "Name"}}</span> {{> icon icon=(sortIcon 'name') }}</div>
</th>
<th class="directory__username js-sort {{#if searchSortBy 'username'}}is-sorting{{/if}}" data-sort="username">
<div class="table-fake-th"><span>{{_ "Username"}}</span> {{> icon icon=(sortIcon 'username') }}</div>
</th>
<th class="directory__user-count js-sort {{#if searchSortBy 'subscribers'}}is-sorting{{/if}}" data-sort="subscribers">
<div class="table-fake-th"><span>{{_ "Subscribers"}}</span> {{> icon icon=(sortIcon 'subscribers') }}</div>
</th>
<th class="table-column-date js-sort {{#if searchSortBy 'createdAt'}}is-sorting{{/if}}" data-sort="createdAt">
<div class="table-fake-th"><span>{{_ "Created_at"}}</span> {{> icon icon=(sortIcon 'createdAt') }}</div>
</th>
<th>
<div class="table-fake-th"><span>{{_ "Description"}}</span></div>
</th>
</tr>
</thead>
<tbody>
{{#each searchResults}}
<tr data-name="{{name}}">
<td>
<div class="rc-table-wrapper">
<div class="rc-table-avatar">{{> avatar username=username}}</div>
<div class="rc-table-info">
<span class="rc-table-title">
{{name}}
</span>
</div>
</div>
</td>
<td>{{username}}</td>
<td>{{subscribers}}</td>
<td>{{createdAt}}</td>
<td>{{description}}</td>
</tr>
{{else}}
{{# with searchText}}
<tr class="table-no-click">
<td colspan="{{sumColumnCount 3 (canViewOtherUserInfo) ($eq searchWorkspace 'external')}}">
{{_ "No_results_found_for"}} {{.}}
</td>
</tr>
{{/with}}
{{/each}}
{{#if isLoading}}
<tr class="table-no-click">
<td class="table-loading-td"
colspan="{{sumColumnCount 3 (canViewOtherUserInfo) ($eq searchWorkspace 'external')}}">
{{> loading}}
</td>
</tr>
{{/if}}
</tbody>
{{/table}}
{{/if}}
</div>
</section>
</template>
23 changes: 22 additions & 1 deletion app/ui/client/views/app/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ function directorySearch(config, cb) {
domain: result.federation && result.federation.peer,
};
}

if (config.type === 'serviceAccounts') {
return {
name: result.name,
username: result.username,
createdAt: timeAgo(result.createdAt, t),
description: result.description,
subscribers: result.subscribers || 0,
domain: result.federation && result.federation.peer,
};
}
return null;
}));
});
Expand Down Expand Up @@ -96,13 +107,20 @@ Template.directory.helpers({
return true;
},
};
const serviceAccountsTab = {
label: t('Service_accounts'),
value: 'serviceAccounts',
condition() {
return true;
},
};
if (searchType.get() === 'channels') {
channelsTab.active = true;
} else {
usersTab.active = true;
}
return {
tabs: [channelsTab, usersTab],
tabs: [channelsTab, usersTab, serviceAccountsTab],
onChange(value) {
results.set([]);
end.set(false);
Expand Down Expand Up @@ -130,6 +148,9 @@ Template.directory.helpers({
if (searchType.get() === 'channels') {
type = 'c';
routeConfig = { name: item.name };
} else if (searchType.get() === 'users') {
type = 'd';
routeConfig = { name: item.username };
} else {
type = 'd';
routeConfig = { name: item.username };
Expand Down
3 changes: 3 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,7 @@
"Search_Page_Size": "Page Size",
"Search_Private_Groups": "Search Private Groups",
"Search_Provider": "Search Provider",
"Search_ServiceAccounts": "Search Service Accounts",
"Search_Users": "Search Users",
"seconds": "seconds",
"Secret_token": "Secret Token",
Expand Down Expand Up @@ -2647,13 +2648,15 @@
"Server_Type": "Server Type",
"Service": "Service",
"Service_account": "Service Account",
"Service_accounts": "Service Accounts",
"Service_account_key": "Service account key",
"Service_account_applied": "Service Accounts approval applications",
"Service_account_created_successfully": "Service Account created successfully",
"Service_account_dashboard": "Service Account Dashboard",
"Service_account_description": "Service Accounts are an upgrade to existing user accounts. You can connect to a large number of users using service account with exclusive features such as broadcast message to all your subscribers at once",
"Service_account_limit": "Service Accounts per user",
"Service_account_login": "Service Account login",
"Service_Accounts_SearchFields": "Fields to consider for service account search",
"Service_account_name_placeholder": "Service Account name",
"Service_account_username_placeholder": "Service Account username",
"Service_account_title": "Create a new Service Account",
Expand Down
45 changes: 44 additions & 1 deletion server/methods/browseChannels.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,20 @@ const sortUsers = function(field, direction) {
}
};

const sortServiceAccounts = function(field, direction) {
switch (field) {
default:
return {
[field]: direction === 'asc' ? 1 : -1,
};
}
};

Meteor.methods({
browseChannels({ text = '', workspace = '', type = 'channels', sortBy = 'name', sortDirection = 'asc', page, offset, limit = 10 }) {
const regex = new RegExp(s.trim(s.escapeRegExp(text)), 'i');

if (!['channels', 'users'].includes(type)) {
if (!['channels', 'users', 'serviceAccounts'].includes(type)) {
return;
}

Expand Down Expand Up @@ -88,6 +97,40 @@ Meteor.methods({
};
}

if (type === 'serviceAccounts') {
const options = {
...pagination,
sort: sortServiceAccounts(sortBy, sortDirection),
fields: {
username: 1,
name: 1,
createdAt: 1,
description: 1,
federation: 1,
},
};

const exceptions = [user.username];
const forcedSearchFields = workspace === 'all' && ['username', 'name', 'description'];

let result;
if (workspace === 'all') {
result = Users.findByActiveServiceAccountsExcept(text, exceptions, forcedSearchFields, options);
} else if (workspace === 'external') {
result = Users.findByActiveExternalServiceAccountsExcept(text, exceptions, options, forcedSearchFields, Federation.localIdentifier);
} else {
result = Users.findByActiveLocalServiceAccountsExcept(text, exceptions, options, forcedSearchFields, Federation.localIdentifier);
}
const total = result.count();
const results = result.fetch();
results.forEach((account) => {
account.subscribers = Rooms.findDirectRoomContainingUsername(account.username).count();
});
return {
total,
results,
};
}
// non-logged id user
if (!user) {
return;
Expand Down
4 changes: 4 additions & 0 deletions server/methods/createDirectMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ Meteor.methods({
upsertSubscription.$set.archived = true;
}

if (to.u !== undefined) {
upsertSubscription.$set.sa = true;
}

Subscriptions.upsert({
rid,
$and: [{ 'u._id': me._id }], // work around to solve problems with upsert and dot
Expand Down

0 comments on commit 117d725

Please sign in to comment.