mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
[MM-56028] Decouple system_users components for drop in replacement of list (#25613)
This commit is contained in:
@@ -25,6 +25,26 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
|
||||
<div
|
||||
className="more-modal__list member-list-holder"
|
||||
>
|
||||
<div
|
||||
className="system-users__filter-row"
|
||||
>
|
||||
<SystemUsersSearch
|
||||
onChange={[Function]}
|
||||
onSearch={[Function]}
|
||||
value=""
|
||||
/>
|
||||
<SystemUsersFilterTeam
|
||||
onChange={[Function]}
|
||||
onFilter={[Function]}
|
||||
options={Array []}
|
||||
value=""
|
||||
/>
|
||||
<SystemUsersFilterRole
|
||||
onChange={[Function]}
|
||||
onFilter={[Function]}
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<Connect(SystemUsersList)
|
||||
enableUserAccessTokens={false}
|
||||
experimentalEnableAuthenticationTransfer={false}
|
||||
@@ -33,7 +53,6 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
|
||||
mfaEnabled={false}
|
||||
nextPage={[Function]}
|
||||
onTermChange={[Function]}
|
||||
renderFilterRow={[Function]}
|
||||
search={[Function]}
|
||||
teamId=""
|
||||
teams={Array []}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
import {Constants, SearchUserTeamFilter, UserFilters} from 'utils/constants';
|
||||
|
||||
import SystemUsers from './system_users';
|
||||
@@ -43,14 +43,14 @@ describe('components/admin_console/system_users', () => {
|
||||
|
||||
test('should match default snapshot', () => {
|
||||
const props = defaultProps;
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('loadDataForTeam() should have called getProfiles', async () => {
|
||||
const getProfiles = jest.fn().mockResolvedValue(undefined);
|
||||
const props = {...defaultProps, actions: {...defaultProps.actions, getProfiles}};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
wrapper.setState({loading: true});
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('components/admin_console/system_users', () => {
|
||||
test('loadDataForTeam() should have called loadProfilesWithoutTeam', async () => {
|
||||
const loadProfilesWithoutTeam = jest.fn().mockResolvedValue(undefined);
|
||||
const props = {...defaultProps, actions: {...defaultProps.actions, loadProfilesWithoutTeam}};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
wrapper.setState({loading: true});
|
||||
|
||||
@@ -91,7 +91,7 @@ describe('components/admin_console/system_users', () => {
|
||||
teamId: SearchUserTeamFilter.ALL_USERS,
|
||||
actions: {...defaultProps.actions, getProfiles},
|
||||
};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
wrapper.setState({loading: true});
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('components/admin_console/system_users', () => {
|
||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||
actions: {...defaultProps.actions, loadProfilesWithoutTeam},
|
||||
};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
wrapper.setState({loading: true});
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('components/admin_console/system_users', () => {
|
||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||
actions: {...defaultProps.actions, searchProfiles},
|
||||
};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
const instance = wrapper.instance() as SystemUserClass;
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('components/admin_console/system_users', () => {
|
||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||
actions: {...defaultProps.actions, searchProfiles},
|
||||
};
|
||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
||||
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||
|
||||
const instance = wrapper.instance() as SystemUserClass;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type ChangeEvent} from 'react';
|
||||
import {FormattedMessage, type IntlShape, injectIntl} from 'react-intl';
|
||||
import React from 'react';
|
||||
import type {ChangeEvent} from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
@@ -13,19 +14,20 @@ import type {ActionFunc} from 'mattermost-redux/types/actions';
|
||||
|
||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||
|
||||
import {Constants, UserSearchOptions, SearchUserTeamFilter, UserFilters} from 'utils/constants';
|
||||
import {Constants, UserSearchOptions, SearchUserTeamFilter} from 'utils/constants';
|
||||
import {getUserOptionsFromFilter, searchUserOptionsFromFilter} from 'utils/filter_users';
|
||||
|
||||
import SystemUsersList from './list';
|
||||
import RevokeSessionsButton from './revoke_sessions_button';
|
||||
import SystemUsersFilterRole from './system_users_filter_role';
|
||||
import SystemUsersFilterTeam from './system_users_filter_team';
|
||||
import SystemUsersList from './system_users_list';
|
||||
import SystemUsersSearch from './system_users_search';
|
||||
|
||||
const USER_ID_LENGTH = 26;
|
||||
const USERS_PER_PAGE = 50;
|
||||
|
||||
type Props = {
|
||||
|
||||
intl: IntlShape;
|
||||
|
||||
/**
|
||||
* Array of team objects
|
||||
*/
|
||||
@@ -168,6 +170,14 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
||||
this.props.actions.setSystemUsersSearch(term, this.props.teamId, this.props.filter);
|
||||
};
|
||||
|
||||
handleSearchFiltersChange = ({searchTerm, teamId, filter}: {searchTerm?: string; teamId?: string; filter?: string}) => {
|
||||
const changedSearchTerm = typeof searchTerm === 'undefined' ? this.props.searchTerm : searchTerm;
|
||||
const changedTeamId = typeof teamId === 'undefined' ? this.props.teamId : teamId;
|
||||
const changedFilter = typeof filter === 'undefined' ? this.props.filter : filter;
|
||||
|
||||
this.props.actions.setSystemUsersSearch(changedSearchTerm, changedTeamId, changedFilter);
|
||||
};
|
||||
|
||||
nextPage = async (page: number) => {
|
||||
const {teamId, filter} = this.props;
|
||||
|
||||
@@ -190,6 +200,56 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
||||
this.setState({loading: false});
|
||||
};
|
||||
|
||||
onSearch = async (term: string) => {
|
||||
this.setState({loading: true});
|
||||
|
||||
const options = {
|
||||
...searchUserOptionsFromFilter(this.props.filter),
|
||||
...this.props.teamId && {team_id: this.props.teamId},
|
||||
...this.props.teamId === SearchUserTeamFilter.NO_TEAM && {
|
||||
[UserSearchOptions.WITHOUT_TEAM]: true,
|
||||
},
|
||||
allow_inactive: true,
|
||||
};
|
||||
|
||||
const {data: profiles} = await this.props.actions.searchProfiles(term, options);
|
||||
if (profiles.length === 0 && term.length === USER_ID_LENGTH) {
|
||||
await this.getUserByTokenOrId(term);
|
||||
}
|
||||
|
||||
this.setState({loading: false});
|
||||
};
|
||||
|
||||
onFilter = async ({teamId, filter}: {teamId?: string; filter?: string}) => {
|
||||
if (this.props.searchTerm) {
|
||||
this.onSearch(this.props.searchTerm);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({loading: true});
|
||||
|
||||
const newTeamId = typeof teamId === 'undefined' ? this.props.teamId : teamId;
|
||||
const newFilter = typeof filter === 'undefined' ? this.props.filter : filter;
|
||||
|
||||
const options = getUserOptionsFromFilter(newFilter);
|
||||
|
||||
if (newTeamId === SearchUserTeamFilter.ALL_USERS) {
|
||||
await Promise.all([
|
||||
this.props.actions.getProfiles(0, Constants.PROFILE_CHUNK_SIZE, options),
|
||||
this.props.actions.getFilteredUsersStats({include_bots: false, include_deleted: true}),
|
||||
]);
|
||||
} else if (newTeamId === SearchUserTeamFilter.NO_TEAM) {
|
||||
await this.props.actions.loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, options);
|
||||
} else {
|
||||
await Promise.all([
|
||||
this.props.actions.loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, newTeamId, options),
|
||||
this.props.actions.getTeamStats(newTeamId),
|
||||
]);
|
||||
}
|
||||
|
||||
this.setState({loading: false});
|
||||
};
|
||||
|
||||
doSearch = debounce(async (term, teamId = this.props.teamId, filter = this.props.filter) => {
|
||||
if (!term) {
|
||||
return;
|
||||
@@ -238,67 +298,6 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
||||
this.getUserById(id);
|
||||
};
|
||||
|
||||
renderFilterRow = (doSearch: ((event: React.FormEvent<HTMLInputElement>) => void) | undefined) => {
|
||||
const teams = this.props.teams.map((team) => (
|
||||
<option
|
||||
key={team.id}
|
||||
value={team.id}
|
||||
>
|
||||
{team.display_name}
|
||||
</option>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className='system-users__filter-row'>
|
||||
<div className='system-users__filter'>
|
||||
<input
|
||||
id='searchUsers'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
|
||||
onInput={doSearch}
|
||||
/>
|
||||
</div>
|
||||
<label>
|
||||
<span className='system-users__team-filter-label'>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.team'
|
||||
defaultMessage='Team:'
|
||||
/>
|
||||
</span>
|
||||
<select
|
||||
className='form-control system-users__team-filter'
|
||||
onChange={this.handleTeamChange}
|
||||
value={this.props.teamId}
|
||||
>
|
||||
<option value={SearchUserTeamFilter.ALL_USERS}>{this.props.intl.formatMessage({id: 'admin.system_users.allUsers', defaultMessage: 'All Users'})}</option>
|
||||
<option value={SearchUserTeamFilter.NO_TEAM}>{this.props.intl.formatMessage({id: 'admin.system_users.noTeams', defaultMessage: 'No Teams'})}</option>
|
||||
{teams}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span className='system-users__filter-label'>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.userStatus'
|
||||
defaultMessage='User Status:'
|
||||
/>
|
||||
</span>
|
||||
<select
|
||||
id='selectUserStatus'
|
||||
className='form-control system-users__filter'
|
||||
value={this.props.filter}
|
||||
onChange={this.handleFilterChange}
|
||||
>
|
||||
<option value=''>{this.props.intl.formatMessage({id: 'admin.system_users.allUsers', defaultMessage: 'All Users'})}</option>
|
||||
<option value={UserFilters.SYSTEM_ADMIN}>{this.props.intl.formatMessage({id: 'admin.system_users.system_admin', defaultMessage: 'System Admin'})}</option>
|
||||
<option value={UserFilters.SYSTEM_GUEST}>{this.props.intl.formatMessage({id: 'admin.system_users.guest', defaultMessage: 'Guest'})}</option>
|
||||
<option value={UserFilters.ACTIVE}>{this.props.intl.formatMessage({id: 'admin.system_users.active', defaultMessage: 'Active'})}</option>
|
||||
<option value={UserFilters.INACTIVE}>{this.props.intl.formatMessage({id: 'admin.system_users.inactive', defaultMessage: 'Inactive'})}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='wrapper--fixed'>
|
||||
@@ -306,18 +305,33 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
||||
<FormattedMessage
|
||||
id='admin.system_users.title'
|
||||
defaultMessage='{siteName} Users'
|
||||
values={{
|
||||
siteName: this.props.siteName,
|
||||
}}
|
||||
values={{siteName: this.props.siteName}}
|
||||
/>
|
||||
<RevokeSessionsButton/>
|
||||
</AdminHeader>
|
||||
<div className='admin-console__wrapper'>
|
||||
<div className='admin-console__content'>
|
||||
<div className='more-modal__list member-list-holder'>
|
||||
<div className='system-users__filter-row'>
|
||||
<SystemUsersSearch
|
||||
value={this.props.searchTerm}
|
||||
onChange={this.handleSearchFiltersChange}
|
||||
onSearch={this.onSearch}
|
||||
/>
|
||||
<SystemUsersFilterTeam
|
||||
options={this.props.teams}
|
||||
value={this.props.teamId}
|
||||
onChange={this.handleSearchFiltersChange}
|
||||
onFilter={this.onFilter}
|
||||
/>
|
||||
<SystemUsersFilterRole
|
||||
value={this.props.filter}
|
||||
onChange={this.handleSearchFiltersChange}
|
||||
onFilter={this.onFilter}
|
||||
/>
|
||||
</div>
|
||||
<SystemUsersList
|
||||
loading={this.state.loading}
|
||||
renderFilterRow={this.renderFilterRow}
|
||||
search={this.doSearch}
|
||||
nextPage={this.nextPage}
|
||||
usersPerPage={USERS_PER_PAGE}
|
||||
@@ -339,4 +353,4 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(SystemUsers);
|
||||
export default SystemUsers;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import type {ChangeEvent} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {UserFilters} from 'utils/constants';
|
||||
|
||||
type Props = {
|
||||
value?: string;
|
||||
onChange: ({searchTerm, teamId, filter}: {searchTerm?: string; teamId?: string; filter?: string}) => void;
|
||||
onFilter: ({teamId, filter}: {teamId?: string; filter?: string}) => Promise<void>;
|
||||
};
|
||||
|
||||
function SystemUsersFilterRole(props: Props) {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLSelectElement>) {
|
||||
const filter = e?.target?.value ?? '';
|
||||
props.onChange({filter});
|
||||
props.onFilter({filter});
|
||||
}
|
||||
|
||||
return (
|
||||
<label>
|
||||
<span className='system-users__filter-label'>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.userStatus'
|
||||
defaultMessage='User Status:'
|
||||
/>
|
||||
</span>
|
||||
<select
|
||||
id='selectUserStatus'
|
||||
className='form-control system-users__filter'
|
||||
value={props.value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value=''>
|
||||
{formatMessage({id: 'admin.system_users.allUsers', defaultMessage: 'All Users'})}
|
||||
</option>
|
||||
<option value={UserFilters.SYSTEM_ADMIN}>
|
||||
{formatMessage({id: 'admin.system_users.system_admin', defaultMessage: 'System Admin'})}
|
||||
</option>
|
||||
<option value={UserFilters.SYSTEM_GUEST}>
|
||||
{formatMessage({id: 'admin.system_users.guest', defaultMessage: 'Guest'})}
|
||||
</option>
|
||||
<option value={UserFilters.ACTIVE}>
|
||||
{formatMessage({id: 'admin.system_users.active', defaultMessage: 'Active'})}
|
||||
</option>
|
||||
<option value={UserFilters.INACTIVE}>
|
||||
{formatMessage({id: 'admin.system_users.inactive', defaultMessage: 'Inactive'})}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export default SystemUsersFilterRole;
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ChangeEvent} from 'react';
|
||||
import React from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import type {Team} from '@mattermost/types/teams';
|
||||
|
||||
import {SearchUserTeamFilter} from 'utils/constants';
|
||||
|
||||
type Props = {
|
||||
options?: Team[];
|
||||
value?: string;
|
||||
onChange: ({searchTerm, teamId, filter}: {searchTerm?: string; teamId?: string; filter?: string}) => void;
|
||||
onFilter: ({teamId, filter}: {teamId?: string; filter?: string}) => Promise<void>;
|
||||
};
|
||||
|
||||
function SystemUsersFilterTeam(props: Props) {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLSelectElement>) {
|
||||
const teamId = e?.target?.value ?? '';
|
||||
props.onChange({teamId});
|
||||
props.onFilter({teamId});
|
||||
}
|
||||
|
||||
return (
|
||||
<label>
|
||||
<span className='system-users__team-filter-label'>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.team'
|
||||
defaultMessage='Team:'
|
||||
/>
|
||||
</span>
|
||||
<select
|
||||
className='form-control system-users__team-filter'
|
||||
value={props.value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value={SearchUserTeamFilter.ALL_USERS}>
|
||||
{formatMessage({
|
||||
id: 'admin.system_users.allUsers',
|
||||
defaultMessage: 'All Users',
|
||||
})}
|
||||
</option>
|
||||
<option value={SearchUserTeamFilter.NO_TEAM}>
|
||||
{formatMessage({
|
||||
id: 'admin.system_users.noTeams',
|
||||
defaultMessage: 'No Teams',
|
||||
})}
|
||||
</option>
|
||||
{props.options?.map((team) => (
|
||||
<option
|
||||
key={team.id}
|
||||
value={team.id}
|
||||
>
|
||||
{team.display_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export default SystemUsersFilterTeam;
|
||||
@@ -33,6 +33,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
isDisabled={false}
|
||||
mfaEnabled={false}
|
||||
nextPage={[Function]}
|
||||
noBuiltInFilters={true}
|
||||
onTermChange={[MockFunction]}
|
||||
page={0}
|
||||
previousPage={[Function]}
|
||||
@@ -46,7 +47,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
"type": [Function],
|
||||
}
|
||||
}
|
||||
search={[Function]}
|
||||
search={[MockFunction]}
|
||||
teamId=""
|
||||
term=""
|
||||
total={0}
|
||||
@@ -214,6 +215,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
isDisabled={false}
|
||||
mfaEnabled={false}
|
||||
nextPage={[Function]}
|
||||
noBuiltInFilters={true}
|
||||
onTermChange={[MockFunction]}
|
||||
page={0}
|
||||
previousPage={[Function]}
|
||||
@@ -227,7 +229,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
"type": [Function],
|
||||
}
|
||||
}
|
||||
search={[Function]}
|
||||
search={[MockFunction]}
|
||||
teamId=""
|
||||
term=""
|
||||
total={0}
|
||||
@@ -441,6 +443,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
isDisabled={false}
|
||||
mfaEnabled={true}
|
||||
nextPage={[Function]}
|
||||
noBuiltInFilters={true}
|
||||
onTermChange={[MockFunction]}
|
||||
page={0}
|
||||
previousPage={[Function]}
|
||||
@@ -454,7 +457,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
||||
"type": [Function],
|
||||
}
|
||||
}
|
||||
search={[Function]}
|
||||
search={[MockFunction]}
|
||||
teamId=""
|
||||
term=""
|
||||
total={0}
|
||||
@@ -6,7 +6,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import * as users from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getUsers} from 'components/admin_console/system_users/list/selectors';
|
||||
import {getUsers} from 'components/admin_console/system_users/system_users_list/selectors';
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/users');
|
||||
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import SystemUsersList from 'components/admin_console/system_users/list/system_users_list';
|
||||
import SystemUsersList from 'components/admin_console/system_users/system_users_list/system_users_list';
|
||||
|
||||
import {Constants} from 'utils/constants';
|
||||
|
||||
@@ -31,7 +31,6 @@ type Props = {
|
||||
nextPage: (page: number) => void;
|
||||
search: (term: string) => void;
|
||||
focusOnMount?: boolean;
|
||||
renderFilterRow: (doSearch: ((event: React.FormEvent<HTMLInputElement>) => void) | undefined) => JSX.Element;
|
||||
|
||||
teamId: string;
|
||||
filter: string;
|
||||
@@ -347,11 +346,9 @@ export default class SystemUsersList extends React.PureComponent<Props, State> {
|
||||
}}
|
||||
nextPage={this.nextPage}
|
||||
previousPage={this.previousPage}
|
||||
search={this.search}
|
||||
page={this.state.page}
|
||||
term={this.props.term}
|
||||
onTermChange={this.props.onTermChange}
|
||||
rowComponentType={UserListRowWithError}
|
||||
noBuiltInFilters={true}
|
||||
/>
|
||||
<ManageTeamsModal
|
||||
user={this.state.user}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import type {ChangeEvent} from 'react';
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import Constants from 'utils/constants';
|
||||
|
||||
type Props = {
|
||||
value?: string;
|
||||
onChange: ({searchTerm, teamId, filter}: {searchTerm?: string; teamId?: string; filter?: string}) => void;
|
||||
onSearch: (value: string) => void;
|
||||
};
|
||||
|
||||
function SystemUsersSearch(props: Props) {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const debouncedSearch = useCallback(debounce((value: string) => {
|
||||
props.onSearch(value);
|
||||
}, Constants.SEARCH_TIMEOUT_MILLISECONDS), []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
debouncedSearch.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
function handleChange(e: ChangeEvent<HTMLInputElement>) {
|
||||
const searchTerm = e?.target?.value?.trim() ?? '';
|
||||
props.onChange({searchTerm});
|
||||
|
||||
if (searchTerm.length > 0) {
|
||||
debouncedSearch(searchTerm);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='system-users__filter'>
|
||||
<input
|
||||
id='searchUsers'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={formatMessage({
|
||||
id: 'filtered_user_list.search',
|
||||
defaultMessage: 'Search users',
|
||||
})}
|
||||
value={props.value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SystemUsersSearch;
|
||||
@@ -24,6 +24,7 @@ type Props = {
|
||||
previousPage: () => void;
|
||||
search: (term: string) => void;
|
||||
actions?: React.ReactNode[];
|
||||
noBuiltInFilters?: boolean;
|
||||
actionProps?: {
|
||||
mfaEnabled: boolean;
|
||||
enableUserAccessTokens: boolean;
|
||||
@@ -274,31 +275,33 @@ class SearchableUserList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
let filterRow;
|
||||
if (this.props.renderFilterRow) {
|
||||
filterRow = this.props.renderFilterRow(this.handleInput);
|
||||
} else {
|
||||
filterRow = (
|
||||
<div className='col-xs-12'>
|
||||
<label
|
||||
className='hidden-label'
|
||||
htmlFor='searchUsersInput'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.search'
|
||||
defaultMessage='Search users'
|
||||
if (!this.props.noBuiltInFilters) {
|
||||
if (this.props.renderFilterRow) {
|
||||
filterRow = this.props.renderFilterRow(this.handleInput);
|
||||
} else {
|
||||
filterRow = (
|
||||
<div className='col-xs-12'>
|
||||
<label
|
||||
className='hidden-label'
|
||||
htmlFor='searchUsersInput'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='filtered_user_list.search'
|
||||
defaultMessage='Search users'
|
||||
/>
|
||||
</label>
|
||||
<QuickInput
|
||||
ref={this.filterRef}
|
||||
id='searchUsersInput'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
|
||||
aria-label={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
|
||||
onInput={this.handleInput}
|
||||
value={this.props.term}
|
||||
/>
|
||||
</label>
|
||||
<QuickInput
|
||||
ref={this.filterRef}
|
||||
id='searchUsersInput'
|
||||
className='form-control filter-textbox'
|
||||
placeholder={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
|
||||
aria-label={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
|
||||
onInput={this.handleInput}
|
||||
value={this.props.term}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user