Plugin groups (#30320)
* add new pluginapi methods * SAML login hook * set ReAddRemovedMembers to true for plugin groups * change to DoLogin signature for SAML
Этот коммит содержится в:
@@ -7,7 +7,7 @@ import type {IntlShape} from 'react-intl';
|
||||
import {injectIntl, FormattedMessage, defineMessage} from 'react-intl';
|
||||
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
import type {Group, SyncablePatch} from '@mattermost/types/groups';
|
||||
import type {Group, GroupSource, SyncablePatch} from '@mattermost/types/groups';
|
||||
import {SyncableType} from '@mattermost/types/groups';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -37,7 +37,7 @@ export type Props = {
|
||||
onAddCallback?: (groupIDs: string[]) => void;
|
||||
|
||||
actions: {
|
||||
getGroupsNotAssociatedToChannel: (channelID: string, q?: string, page?: number, perPage?: number, filterParentTeamPermitted?: boolean) => Promise<ActionResult>;
|
||||
getGroupsNotAssociatedToChannel: (channelID: string, q?: string, page?: number, perPage?: number, filterParentTeamPermitted?: boolean, source?: GroupSource | string, onlySyncableSources?: boolean) => Promise<ActionResult>;
|
||||
setModalSearchTerm: (term: string) => void;
|
||||
linkGroupSyncable: (groupID: string, syncableID: string, syncableType: SyncableType, patch: Partial<SyncablePatch>) => Promise<ActionResult>;
|
||||
getAllGroupsAssociatedToChannel: (channelID: string, filterAllowReference: boolean, includeMemberCount: boolean) => Promise<ActionResult>;
|
||||
@@ -80,7 +80,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
Promise.all([
|
||||
this.props.actions.getTeam(this.props.teamID),
|
||||
this.props.actions.getAllGroupsAssociatedToTeam(this.props.teamID, false, true),
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, '', 0, GROUPS_PER_PAGE + 1, true),
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, '', 0, GROUPS_PER_PAGE + 1, true, '', true),
|
||||
this.props.actions.getAllGroupsAssociatedToChannel(this.props.currentChannelId, false, true),
|
||||
]).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
@@ -99,7 +99,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
this.searchTimeoutId = window.setTimeout(
|
||||
async () => {
|
||||
this.setGroupsLoadingState(true);
|
||||
await this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, searchTerm, undefined, undefined, true);
|
||||
await this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, searchTerm, undefined, undefined, true, '', true);
|
||||
this.setGroupsLoadingState(false);
|
||||
},
|
||||
Constants.SEARCH_TIMEOUT_MILLISECONDS,
|
||||
@@ -168,7 +168,7 @@ export class AddGroupsToChannelModal extends React.PureComponent<Props, State> {
|
||||
handlePageChange = (page: number, prevPage: number) => {
|
||||
if (page > prevPage) {
|
||||
this.setGroupsLoadingState(true);
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, true).then(() => {
|
||||
this.props.actions.getGroupsNotAssociatedToChannel(this.props.currentChannelId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, true, '', true).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Modal} from 'react-bootstrap';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import {injectIntl, FormattedMessage, defineMessage} from 'react-intl';
|
||||
|
||||
import type {Group, SyncablePatch} from '@mattermost/types/groups';
|
||||
import type {Group, SyncablePatch, GroupSource} from '@mattermost/types/groups';
|
||||
import {SyncableType} from '@mattermost/types/groups';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -41,7 +41,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export type Actions = {
|
||||
getGroupsNotAssociatedToTeam: (teamID: string, q?: string, page?: number, perPage?: number) => Promise<ActionResult>;
|
||||
getGroupsNotAssociatedToTeam: (teamID: string, q?: string, page?: number, perPage?: number, source?: GroupSource | string, onlySyncableSources?: boolean) => Promise<ActionResult>;
|
||||
setModalSearchTerm: (term: string) => void;
|
||||
linkGroupSyncable: (groupID: string, syncableID: string, syncableType: SyncableType, patch: SyncablePatch) => Promise<ActionResult>;
|
||||
getAllGroupsAssociatedToTeam: (teamID: string, filterAllowReference: boolean, includeMemberCount: boolean) => Promise<ActionResult>;
|
||||
@@ -79,7 +79,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
|
||||
public componentDidMount() {
|
||||
Promise.all([
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, '', 0, GROUPS_PER_PAGE + 1),
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, '', 0, GROUPS_PER_PAGE + 1, '', true),
|
||||
this.props.actions.getAllGroupsAssociatedToTeam(this.props.currentTeamId, false, true),
|
||||
]).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
@@ -98,7 +98,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
this.searchTimeoutId = window.setTimeout(
|
||||
async () => {
|
||||
this.setGroupsLoadingState(true);
|
||||
await this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, searchTerm);
|
||||
await this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, searchTerm, 0, GROUPS_PER_PAGE + 1, '', true);
|
||||
this.setGroupsLoadingState(false);
|
||||
},
|
||||
Constants.SEARCH_TIMEOUT_MILLISECONDS,
|
||||
@@ -176,7 +176,7 @@ export class AddGroupsToTeamModal extends React.PureComponent<Props, State> {
|
||||
public handlePageChange = (page: number, prevPage: number): void => {
|
||||
if (page > prevPage) {
|
||||
this.setGroupsLoadingState(true);
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1).then(() => {
|
||||
this.props.actions.getGroupsNotAssociatedToTeam(this.props.currentTeamId, this.props.searchTerm, page, GROUPS_PER_PAGE + 1, '', true).then(() => {
|
||||
this.setGroupsLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ For more information on Groups, please see <link>documentation</link>."
|
||||
>
|
||||
<Connect(GroupsList) />
|
||||
</AdminPanel>
|
||||
<Pluggable
|
||||
pluggableName="SystemConsoleGroupTable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1399,3 +1399,224 @@ exports[`components/admin_console/group_settings/group_details/GroupUsers should
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/admin_console/group_settings/group_details/GroupUsers should match snapshot, plugin group 1`] = `
|
||||
<div
|
||||
className="group-users"
|
||||
>
|
||||
<div
|
||||
className="group-users--header"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="This group is managed by a plugin."
|
||||
id="admin.group_settings.group_profile.group_users.pluginGroupText"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="group-users--body"
|
||||
>
|
||||
<div
|
||||
className="group-users-loading active"
|
||||
>
|
||||
<i
|
||||
className="fa fa-spinner fa-pulse fa-2x"
|
||||
/>
|
||||
</div>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name0 Surname0"
|
||||
email="test0@test.com"
|
||||
key="id0"
|
||||
lastPictureUpdate={0}
|
||||
userId="id0"
|
||||
username="username0"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name1 Surname1"
|
||||
email="test1@test.com"
|
||||
key="id1"
|
||||
lastPictureUpdate={1}
|
||||
userId="id1"
|
||||
username="username1"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name2 Surname2"
|
||||
email="test2@test.com"
|
||||
key="id2"
|
||||
lastPictureUpdate={2}
|
||||
userId="id2"
|
||||
username="username2"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name3 Surname3"
|
||||
email="test3@test.com"
|
||||
key="id3"
|
||||
lastPictureUpdate={3}
|
||||
userId="id3"
|
||||
username="username3"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name4 Surname4"
|
||||
email="test4@test.com"
|
||||
key="id4"
|
||||
lastPictureUpdate={4}
|
||||
userId="id4"
|
||||
username="username4"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name5 Surname5"
|
||||
email="test5@test.com"
|
||||
key="id5"
|
||||
lastPictureUpdate={5}
|
||||
userId="id5"
|
||||
username="username5"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name6 Surname6"
|
||||
email="test6@test.com"
|
||||
key="id6"
|
||||
lastPictureUpdate={6}
|
||||
userId="id6"
|
||||
username="username6"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name7 Surname7"
|
||||
email="test7@test.com"
|
||||
key="id7"
|
||||
lastPictureUpdate={7}
|
||||
userId="id7"
|
||||
username="username7"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name8 Surname8"
|
||||
email="test8@test.com"
|
||||
key="id8"
|
||||
lastPictureUpdate={8}
|
||||
userId="id8"
|
||||
username="username8"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name9 Surname9"
|
||||
email="test9@test.com"
|
||||
key="id9"
|
||||
lastPictureUpdate={9}
|
||||
userId="id9"
|
||||
username="username9"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name10 Surname10"
|
||||
email="test10@test.com"
|
||||
key="id10"
|
||||
lastPictureUpdate={10}
|
||||
userId="id10"
|
||||
username="username10"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name11 Surname11"
|
||||
email="test11@test.com"
|
||||
key="id11"
|
||||
lastPictureUpdate={11}
|
||||
userId="id11"
|
||||
username="username11"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name12 Surname12"
|
||||
email="test12@test.com"
|
||||
key="id12"
|
||||
lastPictureUpdate={12}
|
||||
userId="id12"
|
||||
username="username12"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name13 Surname13"
|
||||
email="test13@test.com"
|
||||
key="id13"
|
||||
lastPictureUpdate={13}
|
||||
userId="id13"
|
||||
username="username13"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name14 Surname14"
|
||||
email="test14@test.com"
|
||||
key="id14"
|
||||
lastPictureUpdate={14}
|
||||
userId="id14"
|
||||
username="username14"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name15 Surname15"
|
||||
email="test15@test.com"
|
||||
key="id15"
|
||||
lastPictureUpdate={15}
|
||||
userId="id15"
|
||||
username="username15"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name16 Surname16"
|
||||
email="test16@test.com"
|
||||
key="id16"
|
||||
lastPictureUpdate={16}
|
||||
userId="id16"
|
||||
username="username16"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name17 Surname17"
|
||||
email="test17@test.com"
|
||||
key="id17"
|
||||
lastPictureUpdate={17}
|
||||
userId="id17"
|
||||
username="username17"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name18 Surname18"
|
||||
email="test18@test.com"
|
||||
key="id18"
|
||||
lastPictureUpdate={18}
|
||||
userId="id18"
|
||||
username="username18"
|
||||
/>
|
||||
<Memo(GroupUsersRow)
|
||||
displayName="Name19 Surname19"
|
||||
email="test19@test.com"
|
||||
key="id19"
|
||||
lastPictureUpdate={19}
|
||||
userId="id19"
|
||||
username="username19"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="group-users--footer"
|
||||
>
|
||||
<div
|
||||
className="counter"
|
||||
>
|
||||
<MemoizedFormattedMessage
|
||||
defaultMessage="{startCount, number} - {endCount, number} of {total, number}"
|
||||
id="admin.group_settings.groups_list.paginatorCount"
|
||||
values={
|
||||
Object {
|
||||
"endCount": 20,
|
||||
"startCount": 1,
|
||||
"total": 20,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-tertiary prev disabled"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Memo(PreviousIcon) />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-tertiary next disabled"
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<Memo(NextIcon) />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -761,6 +761,7 @@ class GroupDetails extends React.PureComponent<Props, State> {
|
||||
total={memberCount}
|
||||
groupID={this.props.groupID}
|
||||
getMembers={this.props.actions.getMembers}
|
||||
source={group.source}
|
||||
/>
|
||||
</AdminPanel>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {shallow} from 'enzyme';
|
||||
import range from 'lodash/range';
|
||||
import React from 'react';
|
||||
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import GroupUsers from 'components/admin_console/group_settings/group_details/group_users';
|
||||
@@ -23,6 +24,7 @@ describe('components/admin_console/group_settings/group_details/GroupUsers', ()
|
||||
groupID: 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
members: members.slice(0, 20),
|
||||
total: 20,
|
||||
source: GroupSource.Ldap,
|
||||
getMembers: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
};
|
||||
|
||||
@@ -36,6 +38,16 @@ describe('components/admin_console/group_settings/group_details/GroupUsers', ()
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, plugin group', () => {
|
||||
const wrapper = shallow(
|
||||
<GroupUsers
|
||||
{...defaultProps}
|
||||
source={PluginGroupSourcePrefix.Plugin + 'keycloak'}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot, on loading with data', () => {
|
||||
const wrapper = shallow(<GroupUsers {...defaultProps}/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
@@ -19,6 +20,7 @@ type Props = {
|
||||
groupID: string;
|
||||
members: UserProfile[];
|
||||
total: number;
|
||||
source?: string;
|
||||
getMembers: (
|
||||
id: string,
|
||||
page?: number,
|
||||
@@ -154,23 +156,42 @@ export default class GroupUsers extends React.PureComponent<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
renderHeader = () => {
|
||||
if (this.props.source === GroupSource.Ldap) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.ldapConnectorText'
|
||||
defaultMessage={
|
||||
'AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>'
|
||||
}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
<Link to='/admin_console/authentication/ldap'>
|
||||
{chunks}
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.source?.startsWith(PluginGroupSourcePrefix.Plugin)) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.pluginGroupText'
|
||||
defaultMessage='This group is managed by a plugin.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div className='group-users'>
|
||||
<div className='group-users--header'>
|
||||
<FormattedMessage
|
||||
id='admin.group_settings.group_profile.group_users.ldapConnectorText'
|
||||
defaultMessage={
|
||||
'AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>'
|
||||
}
|
||||
values={{
|
||||
a: (chunks: string) => (
|
||||
<Link to='/admin_console/authentication/ldap'>
|
||||
{chunks}
|
||||
</Link>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{this.renderHeader()}
|
||||
</div>
|
||||
<div className='group-users--body'>
|
||||
<div
|
||||
|
||||
@@ -9,6 +9,7 @@ import ExternalLink from 'components/external_link';
|
||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||
|
||||
import Pluggable from 'plugins/pluggable';
|
||||
import {DocLinks} from 'utils/constants';
|
||||
import {getSiteURL} from 'utils/url';
|
||||
|
||||
@@ -66,6 +67,9 @@ const GroupSettings = ({isDisabled}: Props) => {
|
||||
readOnly={isDisabled}
|
||||
/>
|
||||
</AdminPanel>
|
||||
<Pluggable
|
||||
pluggableName='SystemConsoleGroupTable'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
import React, {useCallback} from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {GroupSource} from '@mattermost/types/groups';
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {getStatusForUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
import {isSyncableSource} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
import StatusIcon from 'components/status_icon';
|
||||
import Avatar from 'components/widgets/users/avatar';
|
||||
@@ -72,7 +72,7 @@ const ViewUserGroupListItem = (props: Props) => {
|
||||
{`@${user.username}`}
|
||||
</div>
|
||||
{
|
||||
(group.source.toLowerCase() !== GroupSource.Ldap && props.permissionToLeaveGroup) &&
|
||||
(!isSyncableSource(group.source.toLowerCase()) && props.permissionToLeaveGroup) &&
|
||||
<button
|
||||
type='button'
|
||||
className='remove-group-member btn btn-icon btn-xs'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Modal} from 'react-bootstrap';
|
||||
import {defineMessage, FormattedMessage} from 'react-intl';
|
||||
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import {GroupSource} from '@mattermost/types/groups';
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
import type {UserProfile} from '@mattermost/types/users';
|
||||
|
||||
import {debounce} from 'mattermost-redux/actions/helpers';
|
||||
@@ -183,6 +183,15 @@ export default class ViewUserGroupModal extends React.PureComponent<Props, State
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
{
|
||||
group.source.toLowerCase().startsWith(PluginGroupSourcePrefix.Plugin) &&
|
||||
<span className='group-source'>
|
||||
<FormattedMessage
|
||||
id='view_user_group_modal.pluginSynced'
|
||||
defaultMessage='Plugin SYNCED'
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1112,6 +1112,7 @@
|
||||
"admin.group_settings.group_profile.group_teams_and_channels.name": "Name",
|
||||
"admin.group_settings.group_profile.group_teams_and_channels.type": "Type",
|
||||
"admin.group_settings.group_profile.group_users.ldapConnectorText": "AD/LDAP Connector is configured to sync and manage this group and its users. <a>Click here to view</a>",
|
||||
"admin.group_settings.group_profile.group_users.pluginGroupText": "This group is managed by a plugin.",
|
||||
"admin.group_settings.group_row.configure": "Configure",
|
||||
"admin.group_settings.group_row.edit": "Edit",
|
||||
"admin.group_settings.group_row.link_failed": "Link failed",
|
||||
@@ -6006,6 +6007,7 @@
|
||||
"view_image.zoom_reset": "Reset Zoom",
|
||||
"view_user_group_modal.ldapSynced": "AD/LDAP SYNCED",
|
||||
"view_user_group_modal.memberCount": "{member_count} {member_count, plural, one {Member} other {Members}}",
|
||||
"view_user_group_modal.pluginSynced": "Plugin SYNCED",
|
||||
"web.footer.about": "About",
|
||||
"web.footer.help": "Help",
|
||||
"web.footer.privacy": "Privacy Policy",
|
||||
|
||||
@@ -171,7 +171,7 @@ export function getGroups(opts: GetGroupsParams) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, source = GroupSource.Ldap) {
|
||||
export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, source: GroupSource | string = GroupSource.Ldap, onlySyncableSources = false) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToTeam,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
@@ -181,11 +181,12 @@ export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, p
|
||||
page,
|
||||
perPage,
|
||||
source,
|
||||
onlySyncableSources,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, filterParentTeamPermitted = false, source = GroupSource.Ldap) {
|
||||
export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT, filterParentTeamPermitted = false, source: GroupSource | string = GroupSource.Ldap, onlySyncableSources = false) {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToChannel,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
@@ -196,6 +197,7 @@ export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page
|
||||
perPage,
|
||||
filterParentTeamPermitted,
|
||||
source,
|
||||
onlySyncableSources,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {createSelector} from 'mattermost-redux/selectors/create_selector';
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import type {UserMentionKey} from 'mattermost-redux/selectors/entities/users';
|
||||
import {filterGroupsMatchingTerm, sortGroups} from 'mattermost-redux/utils/group_utils';
|
||||
import {filterGroupsMatchingTerm, isSyncableSource, sortGroups} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
import {getCurrentUserLocale} from './i18n';
|
||||
|
||||
@@ -197,7 +197,7 @@ export const getGroupsNotAssociatedToTeam: (state: GlobalState, teamID: string)
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID, group]) => !teamGroupIDSet.has(groupID) && group.source === GroupSource.Ldap).map((entry) => entry[1]);
|
||||
return Object.entries(allGroups).filter(([groupID, group]) => !teamGroupIDSet.has(groupID) && isSyncableSource(group.source)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -217,7 +217,7 @@ export const getGroupsNotAssociatedToChannel: (state: GlobalState, channelID: st
|
||||
(state: GlobalState, channelID: string, teamID: string) => getTeam(state, teamID),
|
||||
(state: GlobalState, channelID: string, teamID: string) => getGroupsAssociatedToTeam(state, teamID),
|
||||
(allGroups, channelGroupIDSet, team, teamGroups) => {
|
||||
let result = Object.values(allGroups).filter((group) => !channelGroupIDSet.has(group.id) && group.source === GroupSource.Ldap);
|
||||
let result = Object.values(allGroups).filter((group) => !channelGroupIDSet.has(group.id) && isSyncableSource(group.source));
|
||||
if (team?.group_constrained) {
|
||||
const gids = teamGroups.map((group) => group.id);
|
||||
result = result.filter((group) => gids?.includes(group.id));
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
haveISystemPermission as haveISystemPermissionInternal,
|
||||
} from 'mattermost-redux/selectors/entities/roles_helpers';
|
||||
import {getTeamMemberships, getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {isSyncableSource} from 'mattermost-redux/utils/group_utils';
|
||||
|
||||
// Re-define these types to ensure that these are typed correctly when mattermost-redux is published
|
||||
export const getMySystemPermissions: (state: GlobalState) => Set<string> = getMySystemPermissionsInternal;
|
||||
@@ -86,9 +87,9 @@ export const getGroupListPermissions: (state: GlobalState) => Record<string, Gro
|
||||
const groupPermissionsMap: Record<string, GroupPermissions> = {};
|
||||
groups.forEach((g) => {
|
||||
groupPermissionsMap[g.id] = {
|
||||
can_delete: permissions.has(Permissions.DELETE_CUSTOM_GROUP) && g.source.toLowerCase() !== 'ldap' && g.delete_at === 0,
|
||||
can_manage_members: permissions.has(Permissions.MANAGE_CUSTOM_GROUP_MEMBERS) && g.source.toLowerCase() !== 'ldap' && g.delete_at === 0,
|
||||
can_restore: permissions.has(Permissions.RESTORE_CUSTOM_GROUP) && g.source.toLowerCase() !== 'ldap' && g.delete_at !== 0,
|
||||
can_delete: permissions.has(Permissions.DELETE_CUSTOM_GROUP) && !isSyncableSource(g.source) && g.delete_at === 0,
|
||||
can_manage_members: permissions.has(Permissions.MANAGE_CUSTOM_GROUP_MEMBERS) && !isSyncableSource(g.source) && g.delete_at === 0,
|
||||
can_restore: permissions.has(Permissions.RESTORE_CUSTOM_GROUP) && !isSyncableSource(g.source) && g.delete_at !== 0,
|
||||
};
|
||||
});
|
||||
return groupPermissionsMap;
|
||||
@@ -191,13 +192,13 @@ export const haveIGroupPermission: (state: GlobalState, groupID: string, permiss
|
||||
(state: GlobalState, groupID: string, permission: string) => permission,
|
||||
(systemPermissions, permissionGroups, group, permission) => {
|
||||
if (permission === Permissions.RESTORE_CUSTOM_GROUP) {
|
||||
if ((group.source !== 'ldap' && group.delete_at !== 0) && (systemPermissions.has(permission) || (permissionGroups[group.id] && permissionGroups[group.id].has(permission)))) {
|
||||
if ((!isSyncableSource(group.source) && group.delete_at !== 0) && (systemPermissions.has(permission) || (permissionGroups[group.id] && permissionGroups[group.id].has(permission)))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group.source === 'ldap' || group.delete_at !== 0) {
|
||||
if (isSyncableSource(group.source) || group.delete_at !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Group} from '@mattermost/types/groups';
|
||||
import {GroupSource, PluginGroupSourcePrefix} from '@mattermost/types/groups';
|
||||
|
||||
import {getSuggestionsSplitBy, getSuggestionsSplitByMultiple} from './user_utils';
|
||||
|
||||
import {General} from '../constants';
|
||||
|
||||
export function isSyncableSource(source: string): boolean {
|
||||
return source.toLowerCase() === GroupSource.Ldap || source.toLowerCase().startsWith(PluginGroupSourcePrefix.Plugin);
|
||||
}
|
||||
|
||||
export function filterGroupsMatchingTerm(groups: Group[], term: string): Group[] {
|
||||
const lowercasedTerm = term.toLowerCase();
|
||||
let trimmedTerm = lowercasedTerm;
|
||||
|
||||
@@ -1251,4 +1251,10 @@ export default class PluginRegistry {
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
// Register a component to be displayed in the System Console Groups table.
|
||||
// Accepts a React component. Returns a unique identifier.
|
||||
registerSystemConsoleGroupTable = reArg(['component'], ({component}: DPluginComponentProp) => {
|
||||
return dispatchPluginComponentAction('SystemConsoleGroupTable', this.id, component);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,6 +222,7 @@ const initialComponents: PluginsState['components'] = {
|
||||
MessageWillBePosted: [],
|
||||
MessageWillBeUpdated: [],
|
||||
SlashCommandWillBePosted: [],
|
||||
SystemConsoleGroupTable: [],
|
||||
};
|
||||
|
||||
function components(state: PluginsState['components'] = initialComponents, action: MMAction) {
|
||||
|
||||
@@ -72,6 +72,7 @@ export type PluginsState = {
|
||||
MessageWillBePosted: MessageWillBePostedHook[];
|
||||
SlashCommandWillBePosted: SlashCommandWillBePostedHook[];
|
||||
MessageWillBeUpdated: MessageWillBeUpdatedHook[];
|
||||
SystemConsoleGroupTable: SystemConsoleGroupTableComponent[];
|
||||
};
|
||||
|
||||
postTypes: {
|
||||
@@ -484,3 +485,7 @@ export type AdminConsolePluginCustomSection = {
|
||||
key: string;
|
||||
component: React.Component;
|
||||
};
|
||||
|
||||
export type SystemConsoleGroupTableComponent = PluginComponent & {
|
||||
component: React.ComponentType<BasePluggableProps>;
|
||||
};
|
||||
|
||||
@@ -3713,14 +3713,14 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsNotAssociatedToTeam = (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, source = 'ldap') => {
|
||||
getGroupsNotAssociatedToTeam = (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, source = 'ldap', onlySyncableSources = false) => {
|
||||
return this.doFetch<Group[]>(
|
||||
`${this.getGroupsRoute()}${buildQueryString({not_associated_to_team: teamID, page, per_page: perPage, q, include_member_count: true, group_source: source})}`,
|
||||
`${this.getGroupsRoute()}${buildQueryString({not_associated_to_team: teamID, page, per_page: perPage, q, include_member_count: true, group_source: source, only_syncable_sources: onlySyncableSources})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsNotAssociatedToChannel = (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterParentTeamPermitted = false, source = 'ldap') => {
|
||||
getGroupsNotAssociatedToChannel = (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterParentTeamPermitted = false, source = 'ldap', onlySyncableSources = false) => {
|
||||
const query = {
|
||||
not_associated_to_channel: channelID,
|
||||
page,
|
||||
@@ -3729,6 +3729,7 @@ export default class Client4 {
|
||||
include_member_count: true,
|
||||
filter_parent_team_permitted: filterParentTeamPermitted,
|
||||
group_source: source,
|
||||
only_syncable_sources: onlySyncableSources,
|
||||
};
|
||||
return this.doFetch<Group[]>(
|
||||
`${this.getGroupsRoute()}${buildQueryString(query)}`,
|
||||
|
||||
@@ -43,11 +43,22 @@ export type Group = {
|
||||
member_ids?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Main sources for groups
|
||||
*/
|
||||
export enum GroupSource {
|
||||
Ldap = 'ldap',
|
||||
Custom = 'custom',
|
||||
}
|
||||
|
||||
/**
|
||||
* Special prefixes that can be added to group sources when they come from plugins
|
||||
* Used for identifying plugin-created groups by checking if source starts with this prefix
|
||||
*/
|
||||
export enum PluginGroupSourcePrefix {
|
||||
Plugin = 'plugin_'
|
||||
}
|
||||
|
||||
export type GroupTeam = {
|
||||
team_id: string;
|
||||
team_display_name: string;
|
||||
|
||||
Ссылка в новой задаче
Block a user