Unverified Commit ec3e625a authored by Joram Wilander's avatar Joram Wilander Committed by GitHub
Browse files

Count inactive users towards total count in system console user list (#1228)

parent b6d03114
......@@ -12,3 +12,13 @@ export function setModalSearchTerm(term) {
return {data: true};
};
}
export function setSystemUsersSearch(term, team = '') {
return async (dispatch) => {
dispatch({
type: SearchTypes.SET_SYSTEM_USERS_SEARCH,
data: {term, team},
});
return {data: true};
};
}
......@@ -7,6 +7,10 @@ import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams';
import {getUser, getUserAccessToken} from 'mattermost-redux/actions/users';
import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {Stats} from 'mattermost-redux/constants';
import {setSystemUsersSearch} from 'actions/views/search';
import {SearchUserTeamFilter} from 'utils/constants.jsx';
import SystemUsers from './system_users.jsx';
......@@ -20,10 +24,32 @@ function mapStateToProps(state) {
const enableUserAccessTokens = config.EnableUserAccessTokens === 'true';
const experimentalEnableAuthenticationTransfer = config.ExperimentalEnableAuthenticationTransfer === 'true';
const search = state.views.search.systemUsersSearch;
let totalUsers = 0;
let searchTerm = '';
let teamId = '';
if (search) {
searchTerm = search.term || '';
teamId = search.team || '';
if (!teamId || teamId === SearchUserTeamFilter.ALL_USERS) {
const stats = state.entities.admin.analytics || {[Stats.TOTAL_USERS]: 0, [Stats.TOTAL_INACTIVE_USERS]: 0};
totalUsers = stats[Stats.TOTAL_USERS] + stats[Stats.TOTAL_INACTIVE_USERS];
} else if (teamId === SearchUserTeamFilter.NO_TEAM) {
totalUsers = 0;
} else {
const stats = state.entities.teams.stats[teamId] || {total_member_count: 0};
totalUsers = stats.total_member_count;
}
}
return {
teams: getTeamsList(state),
siteName,
mfaEnabled,
totalUsers,
searchTerm,
teamId,
enableUserAccessTokens,
experimentalEnableAuthenticationTransfer,
};
......@@ -36,6 +62,7 @@ function mapDispatchToProps(dispatch) {
getTeamStats,
getUser,
getUserAccessToken,
setSystemUsersSearch,
}, dispatch),
};
}
......
......@@ -8,17 +8,12 @@ import {FormattedMessage} from 'react-intl';
import {getStandardAnalytics} from 'actions/admin_actions.jsx';
import {reloadIfServerVersionChanged} from 'actions/global_actions.jsx';
import {loadProfiles, loadProfilesAndTeamMembers, loadProfilesWithoutTeam, searchUsers} from 'actions/user_actions.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx';
import {Constants, UserSearchOptions, SearchUserTeamFilter} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import SystemUsersList from './list';
const ALL_USERS = '';
const NO_TEAM = 'no_team';
const USER_ID_LENGTH = 26;
const USERS_PER_PAGE = 50;
......@@ -49,6 +44,9 @@ export default class SystemUsers extends React.Component {
* Whether or not the experimental authentication transfer is enabled.
*/
experimentalEnableAuthenticationTransfer: PropTypes.bool.isRequired,
totalUsers: PropTypes.number.isRequired,
searchTerm: PropTypes.string.isRequired,
teamId: PropTypes.string.isRequired,
actions: PropTypes.shape({
......@@ -71,14 +69,13 @@ export default class SystemUsers extends React.Component {
* Function to get a user access token
*/
getUserAccessToken: PropTypes.func.isRequired,
setSystemUsersSearch: PropTypes.func.isRequired,
}).isRequired,
}
constructor(props) {
super(props);
this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this);
this.loadDataForTeam = this.loadDataForTeam.bind(this);
this.loadComplete = this.loadComplete.bind(this);
......@@ -93,64 +90,30 @@ export default class SystemUsers extends React.Component {
this.renderFilterRow = this.renderFilterRow.bind(this);
this.state = {
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
teamId: ALL_USERS,
term: '',
loading: true,
searching: false,
};
}
componentDidMount() {
AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore);
TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore);
this.loadDataForTeam(this.state.teamId);
this.loadDataForTeam(this.props.teamId);
this.props.actions.getTeams(0, 1000).then(reloadIfServerVersionChanged);
}
UNSAFE_componentWillUpdate(nextProps, nextState) { // eslint-disable-line camelcase
const nextTeamId = nextState.teamId;
if (this.state.teamId !== nextTeamId) {
this.updateTotalUsersFromStore(nextTeamId);
this.loadDataForTeam(nextTeamId);
}
}
componentWillUnmount() {
AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore);
TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore);
}
updateTotalUsersFromStore(teamId = this.state.teamId) {
if (teamId === ALL_USERS) {
this.setState({
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
});
} else if (teamId === NO_TEAM) {
this.setState({
totalUsers: 0,
});
} else {
this.setState({
totalUsers: TeamStore.getStats(teamId).total_member_count,
});
}
this.props.actions.setSystemUsersSearch('', '');
}
loadDataForTeam(teamId) {
if (this.state.term) {
this.search(this.state.term, teamId);
if (this.props.searchTerm) {
this.search(this.props.searchTerm, teamId);
return;
}
if (teamId === ALL_USERS) {
if (teamId === SearchUserTeamFilter.ALL_USERS) {
loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
getStandardAnalytics();
} else if (teamId === NO_TEAM) {
} else if (teamId === SearchUserTeamFilter.NO_TEAM) {
loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
} else {
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete);
......@@ -163,26 +126,28 @@ export default class SystemUsers extends React.Component {
}
handleTeamChange(e) {
this.setState({teamId: e.target.value});
const teamId = e.target.value;
this.loadDataForTeam(teamId);
this.props.actions.setSystemUsersSearch(this.props.searchTerm, teamId);
}
handleTermChange(term) {
this.setState({term});
this.props.actions.setSystemUsersSearch(term, this.props.teamId);
}
nextPage(page) {
// Paging isn't supported while searching
if (this.state.teamId === ALL_USERS) {
if (this.props.teamId === SearchUserTeamFilter.ALL_USERS) {
loadProfiles(page + 1, USERS_PER_PAGE, this.loadComplete);
} else if (this.state.teamId === NO_TEAM) {
} else if (this.props.teamId === SearchUserTeamFilter.NO_TEAM) {
loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
} else {
loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.props.teamId, this.loadComplete);
}
}
search(term, teamId = this.state.teamId) {
search(term, teamId = this.props.teamId) {
if (term === '') {
this.setState({
loading: false,
......@@ -197,14 +162,13 @@ export default class SystemUsers extends React.Component {
doSearch(teamId, term, now = false) {
clearTimeout(this.searchTimeoutId);
this.term = term;
this.setState({loading: true});
const options = {
[UserSearchOptions.ALLOW_INACTIVE]: true,
};
if (teamId === NO_TEAM) {
if (teamId === SearchUserTeamFilter.NO_TEAM) {
options[UserSearchOptions.WITHOUT_TEAM] = true;
}
......@@ -251,7 +215,6 @@ export default class SystemUsers extends React.Component {
const {data} = await this.props.actions.getUserAccessToken(id);
if (data) {
this.term = data.user_id;
this.setState({term: data.user_id});
this.getUserById(data.user_id);
return;
......@@ -294,10 +257,10 @@ export default class SystemUsers extends React.Component {
<select
className='form-control system-users__team-filter'
onChange={this.handleTeamChange}
value={this.state.teamId}
value={this.props.teamId}
>
<option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
<option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
<option value={SearchUserTeamFilter.ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
<option value={SearchUserTeamFilter.NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
{teams}
</select>
</label>
......@@ -324,10 +287,10 @@ export default class SystemUsers extends React.Component {
search={this.search}
nextPage={this.nextPage}
usersPerPage={USERS_PER_PAGE}
total={this.state.totalUsers}
total={this.props.totalUsers}
teams={this.props.teams}
teamId={this.state.teamId}
term={this.state.term}
teamId={this.props.teamId}
term={this.props.searchTerm}
onTermChange={this.handleTermChange}
mfaEnabled={this.props.mfaEnabled}
enableUserAccessTokens={this.props.enableUserAccessTokens}
......
......@@ -15,6 +15,17 @@ function modalSearch(state = '', action) {
}
}
function systemUsersSearch(state = {}, action) {
switch (action.type) {
case SearchTypes.SET_SYSTEM_USERS_SEARCH: {
return action.data;
}
default:
return state;
}
}
export default combineReducers({
modalSearch,
systemUsersSearch,
});
......@@ -32,6 +32,7 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
teamId=""
teams={Array []}
term=""
total={0}
usersPerPage={50}
/>
</div>
......
......@@ -13,11 +13,15 @@ describe('components/admin_console/system_users', () => {
mfaEnabled: false,
enableUserAccessTokens: false,
experimentalEnableAuthenticationTransfer: false,
searchTerm: '',
teamId: '',
totalUsers: 0,
actions: {
getTeams: jest.fn().mockImplementation(() => Promise.resolve()),
getTeamStats: jest.fn().mockImplementation(() => Promise.resolve()),
getUser: jest.fn().mockImplementation(() => Promise.resolve()),
getUserAccessToken: jest.fn().mockImplementation(() => Promise.resolve()),
setSystemUsersSearch: jest.fn().mockImplementation(() => Promise.resolve()),
},
};
......
......@@ -404,8 +404,14 @@ export const StatTypes = keyMirror({
MONTHLY_ACTIVE_USERS: null,
});
export const SearchUserTeamFilter = {
ALL_USERS: '',
NO_TEAM: 'no_team',
};
export const SearchTypes = keyMirror({
SET_MODAL_SEARCH: null,
SET_SYSTEM_USERS_SEARCH: null,
});
export const StorageTypes = keyMirror({
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment