Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Commit

Permalink
PLT-2713/PLT-6028 Added System Users list to System Console (#5882)
Browse files Browse the repository at this point in the history
* PLT-2713 Added ability for admins to list users not in any team

* Updated style of unit test

* Split SearchableUserList to give better control over its properties

* Added users without any teams to the user store

* Added ManageUsers page

* Renamed ManageUsers to SystemUsers

* Added ability to search by user id in SystemUsers page

* Added SystemUsersDropdown

* Removed unnecessary injectIntl

* Created TeamUtils

* Reduced scope of system console heading CSS

* Added team filter to TeamAnalytics page

* Updated admin console sidebar

* Removed unnecessary TODO

* Removed unused reference to deleted modal

* Fixed system console sidebar not scrolling on first load

* Fixed TeamAnalytics page not rendering on first load

* Fixed chart.js throwing an error when switching between teams

* Changed TeamAnalytics header to show the team's display name

* Fixed appearance of TeamAnalytics and SystemUsers on small screen widths

* Fixed placement of 'No users found' message

* Fixed teams not appearing in SystemUsers on first load

* Updated user count text for SystemUsers

* Changed search by id fallback to trigger less often

* Fixed SystemUsers list items not updating when searching

* Fixed localization strings for SystemUsers page
  • Loading branch information
hmhealey authored and coreyhulen committed Mar 30, 2017
1 parent 7de21e2 commit fca82d6
Show file tree
Hide file tree
Showing 74 changed files with 1,436 additions and 1,289 deletions.
3 changes: 2 additions & 1 deletion actions/global_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const ActionTypes = Constants.ActionTypes;
import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as Utils from 'utils/utils.jsx';

import en from 'i18n/en.json';
Expand Down Expand Up @@ -594,7 +595,7 @@ export function redirectUserToDefaultTeam() {
}

if (myTeams.length > 0) {
myTeams = myTeams.sort(Utils.sortTeamsByDisplayName);
myTeams = myTeams.sort(sortTeamsByDisplayName);
teamId = myTeams[0].id;
}
}
Expand Down
62 changes: 40 additions & 22 deletions actions/user_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,29 @@ export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getC
loadTeamMembersForProfiles(list, teamId, success, error);
}

export function loadProfilesWithoutTeam(page, perPage, success, error) {
Client.getProfilesWithoutTeam(
page,
perPage,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM,
profiles: data,
page
});

loadStatusesForProfilesMap(data);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesWithoutTeam');

if (error) {
error(err);
}
}
);
}

function loadTeamMembersForProfiles(userIds, teamId, success, error) {
Client.getTeamMembersByIds(
teamId,
Expand Down Expand Up @@ -580,20 +603,16 @@ export function updateUserNotifyProps(data, success, error) {

export function updateUserRoles(userId, newRoles, success, error) {
Client.updateUserRoles(
userId,
newRoles,
() => {
AsyncClient.getUser(userId);

if (success) {
success();
}
},
(err) => {
if (error) {
error(err);
}
}
userId,
newRoles,
() => {
AsyncClient.getUser(
userId,
success,
error
);
},
error
);
}

Expand Down Expand Up @@ -658,18 +677,17 @@ export function checkMfa(loginId, success, error) {

export function updateActive(userId, active, success, error) {
Client.updateActive(userId, active,
() => {
AsyncClient.getUser(userId);
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILE,
profile: data
});

if (success) {
success();
success(data);
}
},
(err) => {
if (error) {
error(err);
}
}
error
);
}

Expand Down
23 changes: 23 additions & 0 deletions client/client.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,29 @@ export default class Client {
this.trackEvent('api', 'api_profiles_get_not_in_channel', {team_id: this.getTeamId(), channel_id: channelId});
}

getProfilesWithoutTeam(page, perPage, success, error) {
// Super hacky, but this option only exists in api v4
function wrappedSuccess(data, res) {
// Convert the profile list provided by api v4 to a map to match similar v3 calls
const profiles = {};

for (const profile of data) {
profiles[profile.id] = profile;
}

success(profiles, res);
}

request.
get(`${this.url}/api/v4/users?without_team=1&page=${page}&per_page=${perPage}`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
end(this.handleResponse.bind(this, 'getProfilesWithoutTeam', wrappedSuccess, error));

this.trackEvent('api', 'api_profiles_get_without_team');
}

getProfilesByIds(userIds, success, error) {
request.
post(`${this.getUsersRoute()}/ids`).
Expand Down
2 changes: 1 addition & 1 deletion components/admin_console/admin_navbar_dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';

import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
import {sortTeamsByDisplayName} from 'utils/utils.jsx';
import {sortTeamsByDisplayName} from 'utils/team_utils.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';

import {FormattedMessage} from 'react-intl';
Expand Down
4 changes: 3 additions & 1 deletion components/admin_console/admin_settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ export default class AdminSettings extends React.Component {
render() {
return (
<div className='wrapper--fixed'>
{this.renderTitle()}
<h3 className='admin-console-header'>
{this.renderTitle()}
</h3>
<form
className='form-horizontal'
role='form'
Expand Down
175 changes: 23 additions & 152 deletions components/admin_console/admin_sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@

import $ from 'jquery';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import AdminStore from 'stores/admin_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';

import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarTeam from './admin_sidebar_team.jsx';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
import SelectTeamModal from './select_team_modal.jsx';
import AdminSidebarCategory from './admin_sidebar_category.jsx';
import AdminSidebarHeader from './admin_sidebar_header.jsx';
import AdminSidebarSection from './admin_sidebar_section.jsx';

export default class AdminSidebar extends React.Component {
Expand All @@ -27,84 +21,23 @@ export default class AdminSidebar extends React.Component {
constructor(props) {
super(props);

this.handleAllTeamsChange = this.handleAllTeamsChange.bind(this);

this.removeTeam = this.removeTeam.bind(this);

this.showTeamSelect = this.showTeamSelect.bind(this);
this.teamSelectedModal = this.teamSelectedModal.bind(this);
this.teamSelectedModalDismissed = this.teamSelectedModalDismissed.bind(this);

this.updateTitle = this.updateTitle.bind(this);

this.renderAddTeamButton = this.renderAddTeamButton.bind(this);
this.renderTeams = this.renderTeams.bind(this);

this.state = {
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams(),
showSelectModal: false
};
}

componentDidMount() {
AdminStore.addAllTeamsChangeListener(this.handleAllTeamsChange);
AsyncClient.getAllTeams();

this.updateTitle();
}

componentDidUpdate() {
if (!Utils.isMobile()) {
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
}
}

componentWillUnmount() {
AdminStore.removeAllTeamsChangeListener(this.handleAllTeamsChange);
}

handleAllTeamsChange() {
this.setState({
teams: AdminStore.getAllTeams(),
selectedTeams: AdminStore.getSelectedTeams()
});
}

removeTeam(team) {
const selectedTeams = Object.assign({}, this.state.selectedTeams);
Reflect.deleteProperty(selectedTeams, team.id);
AdminStore.saveSelectedTeams(selectedTeams);

this.handleAllTeamsChange();

if (this.context.router.isActive('/admin_console/team/' + team.id)) {
browserHistory.push('/admin_console');
componentDidUpdate() {
if (!Utils.isMobile()) {
$('.admin-sidebar .nav-pills__container').perfectScrollbar();
}
}

showTeamSelect(e) {
e.preventDefault();
this.setState({showSelectModal: true});
}

teamSelectedModal(teamId) {
this.setState({
showSelectModal: false
});

const selectedTeams = Object.assign({}, this.state.selectedTeams);
selectedTeams[teamId] = true;

AdminStore.saveSelectedTeams(selectedTeams);

this.handleAllTeamsChange();
}

teamSelectedModalDismissed() {
this.setState({showSelectModal: false});
}

updateTitle() {
let currentSiteName = '';
if (global.window.mm_config.SiteName != null) {
Expand All @@ -114,79 +47,6 @@ export default class AdminSidebar extends React.Component {
document.title = Utils.localizeMessage('sidebar_right_menu.console', 'System Console') + ' - ' + currentSiteName;
}

renderAddTeamButton() {
const addTeamTooltip = (
<Tooltip id='add-team-tooltip'>
<FormattedMessage
id='admin.sidebar.addTeamSidebar'
defaultMessage='Add team from sidebar menu'
/>
</Tooltip>
);

return (
<span className='menu-icon--right'>
<OverlayTrigger
delayShow={1000}
placement='top'
overlay={addTeamTooltip}
>
<a
href='#'
onClick={this.showTeamSelect}
>
<i
className='fa fa-plus'
/>
</a>
</OverlayTrigger>
</span>
);
}

renderTeams() {
const teams = [];
let teamsArray = [];

Reflect.ownKeys(this.state.selectedTeams).forEach((key) => {
if (this.state.teams[key]) {
teamsArray.push(this.state.teams[key]);
}
});

teamsArray = teamsArray.sort(Utils.sortTeamsByDisplayName);

for (let i = 0; i < teamsArray.length; i++) {
const team = teamsArray[i];
teams.push(
<AdminSidebarTeam
key={team.id}
team={team}
onRemoveTeam={this.removeTeam}
/>
);
}

return (
<AdminSidebarCategory
parentLink='/admin_console'
icon='fa-user'
title={
<FormattedMessage
id='admin.sidebar.teams'
defaultMessage='TEAMS ({count, number})'
values={{
count: Object.keys(this.state.teams).length
}}
/>
}
action={this.renderAddTeamButton()}
>
{teams}
</AdminSidebarCategory>
);
}

render() {
let oauthSettings = null;
let ldapSettings = null;
Expand Down Expand Up @@ -421,6 +281,24 @@ export default class AdminSidebar extends React.Component {
/>
}
/>
<AdminSidebarSection
name='team_analytics'
title={
<FormattedMessage
id='admin.sidebar.statistics'
defaultMessage='Team Statistics'
/>
}
/>
<AdminSidebarSection
name='users'
title={
<FormattedMessage
id='admin.sidebar.users'
defaultMessage='Users'
/>
}
/>
<AdminSidebarSection
name='logs'
title={
Expand Down Expand Up @@ -760,16 +638,9 @@ export default class AdminSidebar extends React.Component {
{metricsSettings}
</AdminSidebarSection>
</AdminSidebarCategory>
{this.renderTeams()}
{otherCategory}
</ul>
</div>
<SelectTeamModal
teams={this.state.teams}
show={this.state.showSelectModal}
onModalSubmit={this.teamSelectedModal}
onModalDismissed={this.teamSelectedModalDismissed}
/>
</div>
);
}
Expand Down
Loading

0 comments on commit fca82d6

Please sign in to comment.