[MM-56028] Decouple system_users components for drop in replacement of list (#25613)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
b1e745894b
Коммит
c7f24dfa06
@@ -25,6 +25,26 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
|
|||||||
<div
|
<div
|
||||||
className="more-modal__list member-list-holder"
|
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)
|
<Connect(SystemUsersList)
|
||||||
enableUserAccessTokens={false}
|
enableUserAccessTokens={false}
|
||||||
experimentalEnableAuthenticationTransfer={false}
|
experimentalEnableAuthenticationTransfer={false}
|
||||||
@@ -33,7 +53,6 @@ exports[`components/admin_console/system_users should match default snapshot 1`]
|
|||||||
mfaEnabled={false}
|
mfaEnabled={false}
|
||||||
nextPage={[Function]}
|
nextPage={[Function]}
|
||||||
onTermChange={[Function]}
|
onTermChange={[Function]}
|
||||||
renderFilterRow={[Function]}
|
|
||||||
search={[Function]}
|
search={[Function]}
|
||||||
teamId=""
|
teamId=""
|
||||||
teams={Array []}
|
teams={Array []}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {shallow} from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
|
|
||||||
import {Constants, SearchUserTeamFilter, UserFilters} from 'utils/constants';
|
import {Constants, SearchUserTeamFilter, UserFilters} from 'utils/constants';
|
||||||
|
|
||||||
import SystemUsers from './system_users';
|
import SystemUsers from './system_users';
|
||||||
@@ -43,14 +43,14 @@ describe('components/admin_console/system_users', () => {
|
|||||||
|
|
||||||
test('should match default snapshot', () => {
|
test('should match default snapshot', () => {
|
||||||
const props = defaultProps;
|
const props = defaultProps;
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadDataForTeam() should have called getProfiles', async () => {
|
test('loadDataForTeam() should have called getProfiles', async () => {
|
||||||
const getProfiles = jest.fn().mockResolvedValue(undefined);
|
const getProfiles = jest.fn().mockResolvedValue(undefined);
|
||||||
const props = {...defaultProps, actions: {...defaultProps.actions, getProfiles}};
|
const props = {...defaultProps, actions: {...defaultProps.actions, getProfiles}};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
wrapper.setState({loading: true});
|
wrapper.setState({loading: true});
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ describe('components/admin_console/system_users', () => {
|
|||||||
test('loadDataForTeam() should have called loadProfilesWithoutTeam', async () => {
|
test('loadDataForTeam() should have called loadProfilesWithoutTeam', async () => {
|
||||||
const loadProfilesWithoutTeam = jest.fn().mockResolvedValue(undefined);
|
const loadProfilesWithoutTeam = jest.fn().mockResolvedValue(undefined);
|
||||||
const props = {...defaultProps, actions: {...defaultProps.actions, loadProfilesWithoutTeam}};
|
const props = {...defaultProps, actions: {...defaultProps.actions, loadProfilesWithoutTeam}};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
wrapper.setState({loading: true});
|
wrapper.setState({loading: true});
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ describe('components/admin_console/system_users', () => {
|
|||||||
teamId: SearchUserTeamFilter.ALL_USERS,
|
teamId: SearchUserTeamFilter.ALL_USERS,
|
||||||
actions: {...defaultProps.actions, getProfiles},
|
actions: {...defaultProps.actions, getProfiles},
|
||||||
};
|
};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
wrapper.setState({loading: true});
|
wrapper.setState({loading: true});
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ describe('components/admin_console/system_users', () => {
|
|||||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||||
actions: {...defaultProps.actions, loadProfilesWithoutTeam},
|
actions: {...defaultProps.actions, loadProfilesWithoutTeam},
|
||||||
};
|
};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
wrapper.setState({loading: true});
|
wrapper.setState({loading: true});
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ describe('components/admin_console/system_users', () => {
|
|||||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||||
actions: {...defaultProps.actions, searchProfiles},
|
actions: {...defaultProps.actions, searchProfiles},
|
||||||
};
|
};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
const instance = wrapper.instance() as SystemUserClass;
|
const instance = wrapper.instance() as SystemUserClass;
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ describe('components/admin_console/system_users', () => {
|
|||||||
teamId: SearchUserTeamFilter.NO_TEAM,
|
teamId: SearchUserTeamFilter.NO_TEAM,
|
||||||
actions: {...defaultProps.actions, searchProfiles},
|
actions: {...defaultProps.actions, searchProfiles},
|
||||||
};
|
};
|
||||||
const wrapper = shallowWithIntl(<SystemUsers {...props}/>);
|
const wrapper = shallow(<SystemUsers {...props}/>);
|
||||||
|
|
||||||
const instance = wrapper.instance() as SystemUserClass;
|
const instance = wrapper.instance() as SystemUserClass;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React, {type ChangeEvent} from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage, type IntlShape, injectIntl} from 'react-intl';
|
import type {ChangeEvent} from 'react';
|
||||||
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {ServerError} from '@mattermost/types/errors';
|
import type {ServerError} from '@mattermost/types/errors';
|
||||||
import type {Team} from '@mattermost/types/teams';
|
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 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 {getUserOptionsFromFilter, searchUserOptionsFromFilter} from 'utils/filter_users';
|
||||||
|
|
||||||
import SystemUsersList from './list';
|
|
||||||
import RevokeSessionsButton from './revoke_sessions_button';
|
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 USER_ID_LENGTH = 26;
|
||||||
const USERS_PER_PAGE = 50;
|
const USERS_PER_PAGE = 50;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
intl: IntlShape;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of team objects
|
* 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);
|
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) => {
|
nextPage = async (page: number) => {
|
||||||
const {teamId, filter} = this.props;
|
const {teamId, filter} = this.props;
|
||||||
|
|
||||||
@@ -190,6 +200,56 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
|||||||
this.setState({loading: false});
|
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) => {
|
doSearch = debounce(async (term, teamId = this.props.teamId, filter = this.props.filter) => {
|
||||||
if (!term) {
|
if (!term) {
|
||||||
return;
|
return;
|
||||||
@@ -238,67 +298,6 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
|||||||
this.getUserById(id);
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className='wrapper--fixed'>
|
<div className='wrapper--fixed'>
|
||||||
@@ -306,18 +305,33 @@ export class SystemUsers extends React.PureComponent<Props, State> {
|
|||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.system_users.title'
|
id='admin.system_users.title'
|
||||||
defaultMessage='{siteName} Users'
|
defaultMessage='{siteName} Users'
|
||||||
values={{
|
values={{siteName: this.props.siteName}}
|
||||||
siteName: this.props.siteName,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<RevokeSessionsButton/>
|
<RevokeSessionsButton/>
|
||||||
</AdminHeader>
|
</AdminHeader>
|
||||||
<div className='admin-console__wrapper'>
|
<div className='admin-console__wrapper'>
|
||||||
<div className='admin-console__content'>
|
<div className='admin-console__content'>
|
||||||
<div className='more-modal__list member-list-holder'>
|
<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
|
<SystemUsersList
|
||||||
loading={this.state.loading}
|
loading={this.state.loading}
|
||||||
renderFilterRow={this.renderFilterRow}
|
|
||||||
search={this.doSearch}
|
search={this.doSearch}
|
||||||
nextPage={this.nextPage}
|
nextPage={this.nextPage}
|
||||||
usersPerPage={USERS_PER_PAGE}
|
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}
|
isDisabled={false}
|
||||||
mfaEnabled={false}
|
mfaEnabled={false}
|
||||||
nextPage={[Function]}
|
nextPage={[Function]}
|
||||||
|
noBuiltInFilters={true}
|
||||||
onTermChange={[MockFunction]}
|
onTermChange={[MockFunction]}
|
||||||
page={0}
|
page={0}
|
||||||
previousPage={[Function]}
|
previousPage={[Function]}
|
||||||
@@ -46,7 +47,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
|||||||
"type": [Function],
|
"type": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search={[Function]}
|
search={[MockFunction]}
|
||||||
teamId=""
|
teamId=""
|
||||||
term=""
|
term=""
|
||||||
total={0}
|
total={0}
|
||||||
@@ -214,6 +215,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
|||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
mfaEnabled={false}
|
mfaEnabled={false}
|
||||||
nextPage={[Function]}
|
nextPage={[Function]}
|
||||||
|
noBuiltInFilters={true}
|
||||||
onTermChange={[MockFunction]}
|
onTermChange={[MockFunction]}
|
||||||
page={0}
|
page={0}
|
||||||
previousPage={[Function]}
|
previousPage={[Function]}
|
||||||
@@ -227,7 +229,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
|||||||
"type": [Function],
|
"type": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search={[Function]}
|
search={[MockFunction]}
|
||||||
teamId=""
|
teamId=""
|
||||||
term=""
|
term=""
|
||||||
total={0}
|
total={0}
|
||||||
@@ -441,6 +443,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
|||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
mfaEnabled={true}
|
mfaEnabled={true}
|
||||||
nextPage={[Function]}
|
nextPage={[Function]}
|
||||||
|
noBuiltInFilters={true}
|
||||||
onTermChange={[MockFunction]}
|
onTermChange={[MockFunction]}
|
||||||
page={0}
|
page={0}
|
||||||
previousPage={[Function]}
|
previousPage={[Function]}
|
||||||
@@ -454,7 +457,7 @@ exports[`components/admin_console/system_users/list should match default snapsho
|
|||||||
"type": [Function],
|
"type": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
search={[Function]}
|
search={[MockFunction]}
|
||||||
teamId=""
|
teamId=""
|
||||||
term=""
|
term=""
|
||||||
total={0}
|
total={0}
|
||||||
@@ -6,7 +6,7 @@ import type {UserProfile} from '@mattermost/types/users';
|
|||||||
|
|
||||||
import * as users from 'mattermost-redux/selectors/entities/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');
|
jest.mock('mattermost-redux/selectors/entities/users');
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import type {UserProfile} from '@mattermost/types/users';
|
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';
|
import {Constants} from 'utils/constants';
|
||||||
|
|
||||||
@@ -31,7 +31,6 @@ type Props = {
|
|||||||
nextPage: (page: number) => void;
|
nextPage: (page: number) => void;
|
||||||
search: (term: string) => void;
|
search: (term: string) => void;
|
||||||
focusOnMount?: boolean;
|
focusOnMount?: boolean;
|
||||||
renderFilterRow: (doSearch: ((event: React.FormEvent<HTMLInputElement>) => void) | undefined) => JSX.Element;
|
|
||||||
|
|
||||||
teamId: string;
|
teamId: string;
|
||||||
filter: string;
|
filter: string;
|
||||||
@@ -347,11 +346,9 @@ export default class SystemUsersList extends React.PureComponent<Props, State> {
|
|||||||
}}
|
}}
|
||||||
nextPage={this.nextPage}
|
nextPage={this.nextPage}
|
||||||
previousPage={this.previousPage}
|
previousPage={this.previousPage}
|
||||||
search={this.search}
|
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
term={this.props.term}
|
|
||||||
onTermChange={this.props.onTermChange}
|
|
||||||
rowComponentType={UserListRowWithError}
|
rowComponentType={UserListRowWithError}
|
||||||
|
noBuiltInFilters={true}
|
||||||
/>
|
/>
|
||||||
<ManageTeamsModal
|
<ManageTeamsModal
|
||||||
user={this.state.user}
|
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;
|
previousPage: () => void;
|
||||||
search: (term: string) => void;
|
search: (term: string) => void;
|
||||||
actions?: React.ReactNode[];
|
actions?: React.ReactNode[];
|
||||||
|
noBuiltInFilters?: boolean;
|
||||||
actionProps?: {
|
actionProps?: {
|
||||||
mfaEnabled: boolean;
|
mfaEnabled: boolean;
|
||||||
enableUserAccessTokens: boolean;
|
enableUserAccessTokens: boolean;
|
||||||
@@ -274,31 +275,33 @@ class SearchableUserList extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let filterRow;
|
let filterRow;
|
||||||
if (this.props.renderFilterRow) {
|
if (!this.props.noBuiltInFilters) {
|
||||||
filterRow = this.props.renderFilterRow(this.handleInput);
|
if (this.props.renderFilterRow) {
|
||||||
} else {
|
filterRow = this.props.renderFilterRow(this.handleInput);
|
||||||
filterRow = (
|
} else {
|
||||||
<div className='col-xs-12'>
|
filterRow = (
|
||||||
<label
|
<div className='col-xs-12'>
|
||||||
className='hidden-label'
|
<label
|
||||||
htmlFor='searchUsersInput'
|
className='hidden-label'
|
||||||
>
|
htmlFor='searchUsersInput'
|
||||||
<FormattedMessage
|
>
|
||||||
id='filtered_user_list.search'
|
<FormattedMessage
|
||||||
defaultMessage='Search users'
|
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>
|
</div>
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user