forked from Ivasoft/mattermost-mobile
[MM-23158] Group Mentions & Invites - Display group mentions in suggestions list in the main channel textbox (#4118)
* groups in group mention * fix sorting issues * address PR comments * fix tests * Update groups.ts * fix nock * fix translations * Adding test * fix linting * update redux functions * Add license check * adddress PR comments * remove lodash import * fix lint problems * revert package.json changes * Address PR comments * address PR comments * fix naming * address PR comments * address PR comments * Address comments found in second PR * getAllGroups updated * address PR comments * Update app/mm-redux/utils/group_utils.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * revert change * remove unneeded actions * Use correct server version * MM-26631: Fix order of group displaying * MM-26633: Fix group mentions not showing up in group constrained team * MM-26636: Group mentions not updating when role updates * MM-26637: Group name not updating right away * Address PR comments * TRY AND catch * address PR comments Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
committed by
GitHub
parent
6c9dc8a917
commit
b6eb3b9349
@@ -5,17 +5,19 @@ import {batchActions} from 'redux-batched-actions';
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
|
||||
import {ChannelTypes, RoleTypes} from '@mm-redux/action_types';
|
||||
import {ChannelTypes, RoleTypes, GroupTypes} from '@mm-redux/action_types';
|
||||
import {
|
||||
fetchMyChannelsAndMembers,
|
||||
getChannelByNameAndTeamName,
|
||||
leaveChannel as serviceLeaveChannel,
|
||||
} from '@mm-redux/actions/channels';
|
||||
import {savePreferences} from '@mm-redux/actions/preferences';
|
||||
import {getLicense} from '@mm-redux/selectors/entities/general';
|
||||
import {selectTeam} from '@mm-redux/actions/teams';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import {General, Preferences} from '@mm-redux/constants';
|
||||
import {getPostIdsInChannel} from '@mm-redux/selectors/entities/posts';
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {
|
||||
getCurrentChannelId,
|
||||
getRedirectChannelNameForTeam,
|
||||
@@ -23,7 +25,7 @@ import {
|
||||
isManuallyUnread,
|
||||
} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {getTeamByName} from '@mm-redux/selectors/entities/teams';
|
||||
import {getTeamByName, getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
|
||||
import {getChannelByName as selectChannelByName, getChannelsIdForTeam} from '@mm-redux/utils/channel_utils';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
@@ -572,6 +574,71 @@ function setLoadMorePostsVisible(visible) {
|
||||
};
|
||||
}
|
||||
|
||||
function loadGroupData() {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const actions = [];
|
||||
const team = getCurrentTeam(state);
|
||||
const serverVersion = state.entities.general.serverVersion;
|
||||
const license = getLicense(state);
|
||||
const hasLicense = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true';
|
||||
|
||||
if (hasLicense && team && isMinimumServerVersion(serverVersion, 5, 24)) {
|
||||
for (let i = 0; i <= MAX_RETRIES; i++) {
|
||||
try {
|
||||
if (team.group_constrained) {
|
||||
const [getAllGroupsAssociatedToChannelsInTeam, getAllGroupsAssociatedToTeam] = await Promise.all([ //eslint-disable-line no-await-in-loop
|
||||
Client4.getAllGroupsAssociatedToChannelsInTeam(team.id, true),
|
||||
Client4.getAllGroupsAssociatedToTeam(team.id, true),
|
||||
]);
|
||||
|
||||
if (getAllGroupsAssociatedToChannelsInTeam.groups) {
|
||||
actions.push({
|
||||
type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM,
|
||||
data: {groupsByChannelId: getAllGroupsAssociatedToChannelsInTeam.groups},
|
||||
});
|
||||
}
|
||||
|
||||
if (getAllGroupsAssociatedToTeam) {
|
||||
actions.push({
|
||||
type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_TEAM,
|
||||
data: {...getAllGroupsAssociatedToTeam, teamID: team.id},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const [getAllGroupsAssociatedToChannelsInTeam, getGroups] = await Promise.all([ //eslint-disable-line no-await-in-loop
|
||||
Client4.getAllGroupsAssociatedToChannelsInTeam(team.id, true),
|
||||
Client4.getGroups(true),
|
||||
]);
|
||||
|
||||
if (getAllGroupsAssociatedToChannelsInTeam.groups) {
|
||||
actions.push({
|
||||
type: GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM,
|
||||
data: {groupsByChannelId: getAllGroupsAssociatedToChannelsInTeam.groups},
|
||||
});
|
||||
}
|
||||
|
||||
if (getGroups) {
|
||||
actions.push({
|
||||
type: GroupTypes.RECEIVED_GROUPS,
|
||||
data: getGroups,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return {error: err};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
dispatch(batchActions(actions, 'BATCH_GROUP_DATA'));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function loadChannelsForTeam(teamId, skipDispatch = false) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@@ -642,6 +709,8 @@ export function loadChannelsForTeam(teamId, skipDispatch = false) {
|
||||
|
||||
dispatch(loadUnreadChannelPosts(data.channels, data.channelMembers));
|
||||
}
|
||||
|
||||
dispatch(loadGroupData());
|
||||
}
|
||||
|
||||
return {data};
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
import {Client4} from '@mm-redux/client';
|
||||
import websocketClient from '@websocket';
|
||||
|
||||
import {ChannelTypes, GeneralTypes, EmojiTypes, PostTypes, PreferenceTypes, TeamTypes, UserTypes, RoleTypes, IntegrationTypes} from '@mm-redux/action_types';
|
||||
import {ChannelTypes, GeneralTypes, EmojiTypes, PostTypes, PreferenceTypes, TeamTypes, UserTypes, RoleTypes, IntegrationTypes, GroupTypes} from '@mm-redux/action_types';
|
||||
import {General, Preferences} from '@mm-redux/constants';
|
||||
import {
|
||||
getAllChannels,
|
||||
@@ -298,6 +297,8 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleRoleUpdatedEvent(msg));
|
||||
case WebsocketEvents.USER_ROLE_UPDATED:
|
||||
return dispatch(handleUserRoleUpdated(msg));
|
||||
case WebsocketEvents.MEMBERROLE_UPDATED:
|
||||
return dispatch(handleUpdateMemberRoleEvent(msg));
|
||||
case WebsocketEvents.CHANNEL_CREATED:
|
||||
return dispatch(handleChannelCreatedEvent(msg));
|
||||
case WebsocketEvents.CHANNEL_DELETED:
|
||||
@@ -341,6 +342,8 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleConfigChangedEvent(msg));
|
||||
case WebsocketEvents.OPEN_DIALOG:
|
||||
return dispatch(handleOpenDialogEvent(msg));
|
||||
case WebsocketEvents.RECEIVED_GROUP:
|
||||
return dispatch(handleGroupUpdatedEvent(msg));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
@@ -958,6 +961,34 @@ function handleChannelSchemeUpdatedEvent(msg: WebSocketMessage) {
|
||||
};
|
||||
}
|
||||
|
||||
function handleUpdateMemberRoleEvent(msg: WebSocketMessage) {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
const memberData = JSON.parse(msg.data.member);
|
||||
const roles = memberData.roles.split(' ');
|
||||
const actions = [];
|
||||
|
||||
try {
|
||||
const newRoles = await Client4.getRolesByNames(roles);
|
||||
if (newRoles.length) {
|
||||
actions.push({
|
||||
type: RoleTypes.RECEIVED_ROLES,
|
||||
data: newRoles,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
actions.push({
|
||||
type: TeamTypes.RECEIVED_MY_TEAM_MEMBER,
|
||||
data: memberData,
|
||||
});
|
||||
|
||||
dispatch(batchActions(actions));
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
function handleDirectAddedEvent(msg: WebSocketMessage) {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
const channelActions = await fetchChannelAndMyMember(msg.broadcast.channel_id);
|
||||
@@ -1142,6 +1173,23 @@ function handleOpenDialogEvent(msg: WebSocketMessage) {
|
||||
};
|
||||
}
|
||||
|
||||
function handleGroupUpdatedEvent(msg: WebSocketMessage) {
|
||||
return (dispatch: DispatchFunc) => {
|
||||
const data = JSON.parse(msg.data.group);
|
||||
dispatch(batchActions([
|
||||
{
|
||||
type: GroupTypes.RECEIVED_GROUP,
|
||||
data,
|
||||
},
|
||||
{
|
||||
type: GroupTypes.RECEIVED_MY_GROUPS,
|
||||
data: [data],
|
||||
},
|
||||
]));
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
// Helpers
|
||||
export async function notVisibleUsersActions(state: GlobalState): Promise<Array<GenericAction>> {
|
||||
if (!isMinimumServerVersion(Client4.getServerVersion(), 5, 23)) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import AtMentionItem from 'app/components/autocomplete/at_mention_item';
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import AutocompleteSectionHeader from 'app/components/autocomplete/autocomplete_section_header';
|
||||
import SpecialMentionItem from 'app/components/autocomplete/special_mention_item';
|
||||
import GroupMentionItem from 'app/components/autocomplete/at_mention_group/at_mention_group';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
@@ -38,6 +39,7 @@ export default class AtMention extends PureComponent {
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
nestedScrollEnabled: PropTypes.bool,
|
||||
useChannelMentions: PropTypes.bool.isRequired,
|
||||
groups: PropTypes.array,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -106,7 +108,7 @@ export default class AtMention extends PureComponent {
|
||||
}
|
||||
|
||||
buildSections = (props) => {
|
||||
const {isSearch, inChannel, outChannel, teamMembers, matchTerm} = props;
|
||||
const {isSearch, inChannel, outChannel, teamMembers, matchTerm, groups} = props;
|
||||
const sections = [];
|
||||
|
||||
if (isSearch) {
|
||||
@@ -126,6 +128,16 @@ export default class AtMention extends PureComponent {
|
||||
});
|
||||
}
|
||||
|
||||
if (groups.length) {
|
||||
sections.push({
|
||||
id: t('suggestion.mention.groups'),
|
||||
defaultMessage: 'Group Mentions',
|
||||
data: groups,
|
||||
key: 'groups',
|
||||
renderItem: this.renderGroupMentions,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.useChannelMentions && this.checkSpecialMentions(matchTerm)) {
|
||||
sections.push({
|
||||
id: t('suggestion.mention.special'),
|
||||
@@ -186,7 +198,6 @@ export default class AtMention extends PureComponent {
|
||||
} else {
|
||||
completedDraft = mentionPart.replace(AT_MENTION_REGEX, `@${mention} `);
|
||||
}
|
||||
|
||||
if (value.length > cursorPosition) {
|
||||
completedDraft += value.substring(cursorPosition);
|
||||
}
|
||||
@@ -228,10 +239,20 @@ export default class AtMention extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
renderGroupMentions = ({item}) => {
|
||||
return (
|
||||
<GroupMentionItem
|
||||
key={`autocomplete-group-${item.name}`}
|
||||
completeHandle={item.name}
|
||||
onPress={this.completeMention}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {maxListHeight, theme, nestedScrollEnabled} = this.props;
|
||||
const {mentionComplete, sections} = this.state;
|
||||
|
||||
if (sections.length === 0 || mentionComplete) {
|
||||
// If we are not in an active state or the mention has been completed return null so nothing is rendered
|
||||
// other components are not blocked.
|
||||
|
||||
@@ -6,7 +6,9 @@ import {connect} from 'react-redux';
|
||||
|
||||
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
||||
import {autocompleteUsers} from '@mm-redux/actions/users';
|
||||
import {getLicense} from '@mm-redux/selectors/entities/general';
|
||||
import {getCurrentChannelId, getDefaultChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getAssociatedGroupsForReference, searchAssociatedGroupsForReferenceLocal} from '@mm-redux/selectors/entities/groups';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {isLandscape} from 'app/selectors/device';
|
||||
|
||||
@@ -26,7 +28,9 @@ import AtMention from './at_mention';
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const {cursorPosition, isSearch} = ownProps;
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const license = getLicense(state);
|
||||
const hasLicense = license?.IsLicensed === 'true' && license?.LDAPGroups === 'true';
|
||||
let useChannelMentions = true;
|
||||
if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) {
|
||||
useChannelMentions = haveIChannelPermission(
|
||||
@@ -45,6 +49,7 @@ function mapStateToProps(state, ownProps) {
|
||||
let teamMembers;
|
||||
let inChannel;
|
||||
let outChannel;
|
||||
let groups = [];
|
||||
if (isSearch) {
|
||||
teamMembers = filterMembersInCurrentTeam(state, matchTerm);
|
||||
} else {
|
||||
@@ -52,9 +57,17 @@ function mapStateToProps(state, ownProps) {
|
||||
outChannel = filterMembersNotInChannel(state, matchTerm);
|
||||
}
|
||||
|
||||
if (hasLicense && isMinimumServerVersion(state.entities.general.serverVersion, 5, 24)) {
|
||||
if (matchTerm) {
|
||||
groups = searchAssociatedGroupsForReferenceLocal(state, matchTerm, currentTeamId, currentChannelId);
|
||||
} else {
|
||||
groups = getAssociatedGroupsForReference(state, currentTeamId, currentChannelId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
currentChannelId,
|
||||
currentTeamId: getCurrentTeamId(state),
|
||||
currentTeamId,
|
||||
defaultChannel: getDefaultChannel(state),
|
||||
matchTerm,
|
||||
teamMembers,
|
||||
@@ -64,6 +77,7 @@ function mapStateToProps(state, ownProps) {
|
||||
theme: getTheme(state),
|
||||
isLandscape: isLandscape(state),
|
||||
useChannelMentions,
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import Icon from 'react-native-vector-icons/FontAwesome';
|
||||
|
||||
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from 'app/utils/theme';
|
||||
|
||||
export default class GroupMentionItem extends PureComponent {
|
||||
static propTypes = {
|
||||
completeHandle: PropTypes.string.isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
completeMention = () => {
|
||||
const {onPress, completeHandle} = this.props;
|
||||
onPress(completeHandle);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
completeHandle,
|
||||
theme,
|
||||
} = this.props;
|
||||
|
||||
const style = getStyleFromTheme(theme);
|
||||
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.completeMention}
|
||||
style={style.row}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.rowPicture}>
|
||||
<Icon
|
||||
name='users'
|
||||
style={style.rowIcon}
|
||||
/>
|
||||
</View>
|
||||
<Text style={style.rowUsername}>{`@${completeHandle}`}</Text>
|
||||
<Text style={style.rowUsername}>{' - '}</Text>
|
||||
<Text style={style.rowFullname}>{`${completeHandle}`}</Text>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
row: {
|
||||
paddingVertical: 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
rowPicture: {
|
||||
marginHorizontal: 8,
|
||||
width: 20,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
rowIcon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.7),
|
||||
fontSize: 14,
|
||||
},
|
||||
rowUsername: {
|
||||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
rowFullname: {
|
||||
color: theme.centerChannelColor,
|
||||
flex: 1,
|
||||
opacity: 0.6,
|
||||
},
|
||||
textWrapper: {
|
||||
flex: 1,
|
||||
flexWrap: 'wrap',
|
||||
paddingRight: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -41,5 +41,7 @@ const WebsocketEvents = {
|
||||
PLUGIN_STATUSES_CHANGED: 'plugin_statuses_changed',
|
||||
OPEN_DIALOG: 'open_dialog',
|
||||
INCREASE_POST_VISIBILITY_BY_ONE: 'increase_post_visibility_by_one',
|
||||
MEMBERROLE_UPDATED: 'memberrole_updated',
|
||||
RECEIVED_GROUP: 'received_group',
|
||||
};
|
||||
export default WebsocketEvents;
|
||||
|
||||
@@ -11,9 +11,14 @@ export default keyMirror({
|
||||
|
||||
RECEIVED_GROUP_TEAMS: null,
|
||||
RECEIVED_GROUP_CHANNELS: null,
|
||||
RECEIVED_MY_GROUPS: null,
|
||||
|
||||
RECEIVED_GROUP_MEMBERS: null,
|
||||
|
||||
RECEIVED_GROUP_ASSOCIATED_TO_TEAM: null,
|
||||
|
||||
RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM: null,
|
||||
|
||||
RECEIVED_GROUP: null,
|
||||
|
||||
RECEIVED_GROUPS: null,
|
||||
|
||||
@@ -1,693 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import assert from 'assert';
|
||||
import nock from 'nock';
|
||||
|
||||
import * as Actions from '@mm-redux/actions/groups';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
|
||||
import {Groups} from '../constants';
|
||||
import TestHelper from 'test/test_helper';
|
||||
import configureStore from 'test/test_store';
|
||||
|
||||
describe('Actions.Groups', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestHelper.initBasic(Client4);
|
||||
store = await configureStore();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await TestHelper.tearDown();
|
||||
});
|
||||
|
||||
it('getGroupSyncables', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
|
||||
const groupTeams = [
|
||||
{
|
||||
team_id: 'ge63nq31sbfy3duzq5f7yqn1kh',
|
||||
team_display_name: 'dolphins',
|
||||
team_type: 'O',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542643748412,
|
||||
delete_at: 0,
|
||||
update_at: 1542643748412,
|
||||
},
|
||||
{
|
||||
team_id: 'tdjrcr3hg7yazyos17a53jduna',
|
||||
team_display_name: 'developers',
|
||||
team_type: 'O',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542643825026,
|
||||
delete_at: 0,
|
||||
update_at: 1542643825026,
|
||||
},
|
||||
];
|
||||
|
||||
const groupChannels = [
|
||||
{
|
||||
channel_id: 'o3tdawqxot8kikzq8bk54zggbc',
|
||||
channel_display_name: 'standup',
|
||||
channel_type: 'P',
|
||||
team_id: 'tdjrcr3hg7yazyos17a53jduna',
|
||||
team_display_name: 'developers',
|
||||
team_type: 'O',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542644105041,
|
||||
delete_at: 0,
|
||||
update_at: 1542644105041,
|
||||
},
|
||||
{
|
||||
channel_id: 's6oxu3embpdepyprx1fn5gjhea',
|
||||
channel_display_name: 'swimming',
|
||||
channel_type: 'P',
|
||||
team_id: 'ge63nq31sbfy3duzq5f7yqn1kh',
|
||||
team_display_name: 'dolphins',
|
||||
team_type: 'O',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542644105042,
|
||||
delete_at: 0,
|
||||
update_at: 1542644105042,
|
||||
},
|
||||
];
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups/${groupID}/teams`).
|
||||
reply(200, groupTeams);
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups/${groupID}/channels`).
|
||||
reply(200, groupChannels);
|
||||
|
||||
await Actions.getGroupSyncables(groupID, Groups.SYNCABLE_TYPE_TEAM)(store.dispatch, store.getState);
|
||||
await Actions.getGroupSyncables(groupID, Groups.SYNCABLE_TYPE_CHANNEL)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupSyncables = state.entities.groups.syncables[groupID];
|
||||
assert.ok(groupSyncables);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
assert.ok(JSON.stringify(groupSyncables.teams[i]) === JSON.stringify(groupTeams[i]));
|
||||
assert.ok(JSON.stringify(groupSyncables.channels[i]) === JSON.stringify(groupChannels[i]));
|
||||
}
|
||||
});
|
||||
|
||||
it('getGroupMembers', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
|
||||
const response = {
|
||||
members: [
|
||||
{
|
||||
id: 'ok1mtgwrn7gbzetzfdgircykir',
|
||||
create_at: 1542658437708,
|
||||
update_at: 1542658441412,
|
||||
delete_at: 0,
|
||||
username: 'test.161927',
|
||||
auth_data: 'test.161927',
|
||||
auth_service: 'ldap',
|
||||
email: 'success+test.161927@simulator.amazonses.com',
|
||||
email_verified: true,
|
||||
nickname: '',
|
||||
first_name: 'test',
|
||||
last_name: 'test.161927',
|
||||
position: '',
|
||||
roles: 'system_user',
|
||||
notify_props: {
|
||||
channel: 'true',
|
||||
comments: 'never',
|
||||
desktop: 'mention',
|
||||
desktop_sound: 'true',
|
||||
email: 'true',
|
||||
first_name: 'false',
|
||||
mention_keys: 'test.161927,@test.161927',
|
||||
push: 'mention',
|
||||
push_status: 'away',
|
||||
},
|
||||
last_password_update: 1542658437708,
|
||||
locale: 'en',
|
||||
timezone: {
|
||||
automaticTimezone: '',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: 'true',
|
||||
},
|
||||
},
|
||||
],
|
||||
total_member_count: 1,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups/${groupID}/members?page=0&per_page=100`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroupMembers(groupID, 0, 100)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupMembers = state.entities.groups.members;
|
||||
assert.ok(groupMembers);
|
||||
assert.ok(groupMembers[groupID].totalMemberCount === response.total_member_count);
|
||||
|
||||
assert.ok(JSON.stringify(response.members[0]) === JSON.stringify(groupMembers[groupID].members[0]));
|
||||
});
|
||||
|
||||
it('getGroup', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
|
||||
const response = {
|
||||
id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
name: '8b7ks7ngqbgndqutka48gfzaqh',
|
||||
display_name: 'Test Group 0',
|
||||
description: '',
|
||||
type: 'ldap',
|
||||
remote_id: '\\eb\\80\\94\\cd\\d4\\32\\7c\\45\\87\\79\\1b\\fe\\45\\d9\\ac\\7b',
|
||||
create_at: 1542399032816,
|
||||
update_at: 1542399032816,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups/${groupID}`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroup(groupID)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groups = state.entities.groups.groups;
|
||||
assert.ok(groups);
|
||||
assert.ok(groups[groupID]);
|
||||
assert.ok(JSON.stringify(response) === JSON.stringify(groups[groupID]));
|
||||
});
|
||||
|
||||
it('linkGroupSyncable', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
const teamID = 'ge63nq31sbfy3duzq5f7yqn1kh';
|
||||
const channelID = 'o3tdawqxot8kikzq8bk54zggbc';
|
||||
|
||||
const groupTeamResponse = {
|
||||
team_id: 'ge63nq31sbfy3duzq5f7yqn1kh',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542643748412,
|
||||
delete_at: 0,
|
||||
update_at: 1542660566032,
|
||||
};
|
||||
|
||||
const groupChannelResponse = {
|
||||
channel_id: 'o3tdawqxot8kikzq8bk54zggbc',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542644105041,
|
||||
delete_at: 0,
|
||||
update_at: 1542662607342,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
post(`/groups/${groupID}/teams/${teamID}/link`).
|
||||
reply(200, groupTeamResponse);
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
post(`/groups/${groupID}/channels/${channelID}/link`).
|
||||
reply(200, groupChannelResponse);
|
||||
|
||||
await Actions.linkGroupSyncable(groupID, teamID, Groups.SYNCABLE_TYPE_TEAM)(store.dispatch, store.getState);
|
||||
await Actions.linkGroupSyncable(groupID, channelID, Groups.SYNCABLE_TYPE_CHANNEL)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
const syncables = state.entities.groups.syncables;
|
||||
assert.ok(syncables[groupID]);
|
||||
|
||||
assert.ok(JSON.stringify(syncables[groupID].teams[0]) === JSON.stringify(groupTeamResponse));
|
||||
assert.ok(JSON.stringify(syncables[groupID].channels[0]) === JSON.stringify(groupChannelResponse));
|
||||
});
|
||||
|
||||
it('unlinkGroupSyncable', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
const teamID = 'ge63nq31sbfy3duzq5f7yqn1kh';
|
||||
const channelID = 'o3tdawqxot8kikzq8bk54zggbc';
|
||||
|
||||
const groupTeamResponse = {
|
||||
team_id: 'ge63nq31sbfy3duzq5f7yqn1kh',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542643748412,
|
||||
delete_at: 0,
|
||||
update_at: 1542660566032,
|
||||
};
|
||||
|
||||
const groupChannelResponse = {
|
||||
channel_id: 'o3tdawqxot8kikzq8bk54zggbc',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
create_at: 1542644105041,
|
||||
delete_at: 0,
|
||||
update_at: 1542662607342,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
post(`/groups/${groupID}/teams/${teamID}/link`).
|
||||
reply(200, groupTeamResponse);
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
post(`/groups/${groupID}/channels/${channelID}/link`).
|
||||
reply(200, groupChannelResponse);
|
||||
|
||||
await Actions.linkGroupSyncable(groupID, teamID, Groups.SYNCABLE_TYPE_TEAM)(store.dispatch, store.getState);
|
||||
await Actions.linkGroupSyncable(groupID, channelID, Groups.SYNCABLE_TYPE_CHANNEL)(store.dispatch, store.getState);
|
||||
|
||||
let state = store.getState();
|
||||
let syncables = state.entities.groups.syncables;
|
||||
assert.ok(syncables[groupID]);
|
||||
|
||||
assert.ok(JSON.stringify(syncables[groupID].teams[0]) === JSON.stringify(groupTeamResponse));
|
||||
assert.ok(JSON.stringify(syncables[groupID].channels[0]) === JSON.stringify(groupChannelResponse));
|
||||
|
||||
const beforeTeamsLength = syncables[groupID].teams.length;
|
||||
const beforeChannelsLength = syncables[groupID].channels.length;
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
delete(`/groups/${groupID}/teams/${teamID}/link`).
|
||||
reply(204, {ok: true});
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
delete(`/groups/${groupID}/channels/${channelID}/link`).
|
||||
reply(204, {ok: true});
|
||||
|
||||
await Actions.unlinkGroupSyncable(groupID, teamID, Groups.SYNCABLE_TYPE_TEAM)(store.dispatch, store.getState);
|
||||
await Actions.unlinkGroupSyncable(groupID, channelID, Groups.SYNCABLE_TYPE_CHANNEL)(store.dispatch, store.getState);
|
||||
|
||||
state = store.getState();
|
||||
syncables = state.entities.groups.syncables;
|
||||
|
||||
assert.ok(syncables[groupID]);
|
||||
assert.ok(syncables[groupID].teams.length === beforeTeamsLength - 1);
|
||||
assert.ok(syncables[groupID].channels.length === beforeChannelsLength - 1);
|
||||
});
|
||||
|
||||
it('getAllGroupsAssociatedToTeam', async () => {
|
||||
const teamID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
const response = {
|
||||
groups: [
|
||||
{
|
||||
id: 'xh585kyz3tn55q6ipfo57btwnc',
|
||||
name: '9uobsi3xb3y5tfjb3ze7umnh1o',
|
||||
display_name: 'abc',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'abc',
|
||||
create_at: 1553808969975,
|
||||
update_at: 1553808969975,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
{
|
||||
id: 'tnd8zod9f3fdtqosxjmhwucbth',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
{
|
||||
id: 'qhdp6g7aubbpiyja7c4sgpe7tc',
|
||||
name: 'x5bjwa4kwirpmqudhp5dterine',
|
||||
display_name: 'qa',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'qa',
|
||||
create_at: 1553808971548,
|
||||
update_at: 1553808971548,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
],
|
||||
total_group_count: 3,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/teams/${teamID}/groups?paginate=false`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getAllGroupsAssociatedToTeam(teamID)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupIDs = state.entities.teams.groupsAssociatedToTeam[teamID].ids;
|
||||
assert.strictEqual(groupIDs.length, response.groups.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(response.groups.map((group) => group.id).includes(id));
|
||||
});
|
||||
});
|
||||
|
||||
it('getGroupsAssociatedToTeam', async () => {
|
||||
const teamID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
store = await configureStore({
|
||||
entities: {
|
||||
teams: {
|
||||
groupsAssociatedToTeam: {
|
||||
[teamID]: ['tnd8zod9f3fdtqosxjmhwucbth', 'qhdp6g7aubbpiyja7c4sgpe7tc'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = {
|
||||
groups: [
|
||||
{
|
||||
id: 'tnd8zod9f3fdtqosxjmhwucbth',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
{
|
||||
id: 'qhdp6g7aubbpiyja7c4sgpe7tc',
|
||||
name: 'x5bjwa4kwirpmqudhp5dterine',
|
||||
display_name: 'qa',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'qa',
|
||||
create_at: 1553808971548,
|
||||
update_at: 1553808971548,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
],
|
||||
total_group_count: 3,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/teams/${teamID}/groups?page=100&per_page=60&q=0&include_member_count=true`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroupsAssociatedToTeam(teamID, 0, 100)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupIDs = state.entities.teams.groupsAssociatedToTeam[teamID].ids;
|
||||
const expectedIDs = ['tnd8zod9f3fdtqosxjmhwucbth', 'qhdp6g7aubbpiyja7c4sgpe7tc'];
|
||||
assert.strictEqual(groupIDs.length, expectedIDs.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(expectedIDs.includes(id));
|
||||
assert.ok(state.entities.groups.groups[id]);
|
||||
});
|
||||
|
||||
const count = state.entities.teams.groupsAssociatedToTeam[teamID].totalCount;
|
||||
assert.equal(count, response.total_group_count);
|
||||
});
|
||||
|
||||
it('getGroupsNotAssociatedToTeam', async () => {
|
||||
const teamID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
store = await configureStore({
|
||||
entities: {
|
||||
teams: {
|
||||
groupsAssociatedToTeam: {
|
||||
[teamID]: {ids: ['existing1', 'existing2']},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = [
|
||||
{
|
||||
id: 'existing1',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
];
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups?not_associated_to_team=${teamID}&page=100&per_page=60&q=0&include_member_count=true`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroupsNotAssociatedToTeam(teamID, 0, 100)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
const groupIDs = state.entities.teams.groupsAssociatedToTeam[teamID].ids;
|
||||
const expectedIDs = ['existing2'].concat(response.map((group) => group.id));
|
||||
assert.strictEqual(groupIDs.length, expectedIDs.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(expectedIDs.includes(id));
|
||||
});
|
||||
});
|
||||
|
||||
it('getAllGroupsAssociatedToChannel', async () => {
|
||||
const channelID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
const response = {
|
||||
groups: [
|
||||
{
|
||||
id: 'xh585kyz3tn55q6ipfo57btwnc',
|
||||
name: '9uobsi3xb3y5tfjb3ze7umnh1o',
|
||||
display_name: 'abc',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'abc',
|
||||
create_at: 1553808969975,
|
||||
update_at: 1553808969975,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
{
|
||||
id: 'tnd8zod9f3fdtqosxjmhwucbth',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
{
|
||||
id: 'qhdp6g7aubbpiyja7c4sgpe7tc',
|
||||
name: 'x5bjwa4kwirpmqudhp5dterine',
|
||||
display_name: 'qa',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'qa',
|
||||
create_at: 1553808971548,
|
||||
update_at: 1553808971548,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
],
|
||||
total_group_count: 3,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/channels/${channelID}/groups?paginate=false`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getAllGroupsAssociatedToChannel(channelID)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupIDs = state.entities.channels.groupsAssociatedToChannel[channelID].ids;
|
||||
assert.strictEqual(groupIDs.length, response.groups.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(response.groups.map((group) => group.id).includes(id));
|
||||
});
|
||||
});
|
||||
|
||||
it('getGroupsAssociatedToChannel', async () => {
|
||||
const channelID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
store = await configureStore({
|
||||
entities: {
|
||||
channels: {
|
||||
groupsAssociatedToChannel: {
|
||||
[channelID]: ['tnd8zod9f3fdtqosxjmhwucbth', 'qhdp6g7aubbpiyja7c4sgpe7tc'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = {
|
||||
groups: [
|
||||
{
|
||||
id: 'tnd8zod9f3fdtqosxjmhwucbth',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
{
|
||||
id: 'qhdp6g7aubbpiyja7c4sgpe7tc',
|
||||
name: 'x5bjwa4kwirpmqudhp5dterine',
|
||||
display_name: 'qa',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'qa',
|
||||
create_at: 1553808971548,
|
||||
update_at: 1553808971548,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
},
|
||||
],
|
||||
total_group_count: 3,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/channels/${channelID}/groups?page=100&per_page=60&q=0&include_member_count=true`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroupsAssociatedToChannel(channelID, 0, 100)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupIDs = state.entities.channels.groupsAssociatedToChannel[channelID].ids;
|
||||
const expectedIDs = ['tnd8zod9f3fdtqosxjmhwucbth', 'qhdp6g7aubbpiyja7c4sgpe7tc'];
|
||||
assert.strictEqual(groupIDs.length, expectedIDs.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(expectedIDs.includes(id));
|
||||
assert.ok(state.entities.groups.groups[id]);
|
||||
});
|
||||
|
||||
const count = state.entities.channels.groupsAssociatedToChannel[channelID].totalCount;
|
||||
assert.equal(count, response.total_group_count);
|
||||
});
|
||||
|
||||
it('getGroupsNotAssociatedToChannel', async () => {
|
||||
const channelID = '5rgoajywb3nfbdtyafbod47ryb';
|
||||
|
||||
store = await configureStore({
|
||||
entities: {
|
||||
channels: {
|
||||
groupsAssociatedToChannel: {
|
||||
[channelID]: {ids: ['existing1', 'existing2']},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = [
|
||||
{
|
||||
id: 'existing1',
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
source: 'ldap',
|
||||
remote_id: 'engineering',
|
||||
create_at: 1553808971099,
|
||||
update_at: 1553808971099,
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
},
|
||||
];
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
get(`/groups?not_associated_to_channel=${channelID}&page=100&per_page=60&q=0&include_member_count=true`).
|
||||
reply(200, response);
|
||||
|
||||
await Actions.getGroupsNotAssociatedToChannel(channelID, 0, 100)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
|
||||
const groupIDs = state.entities.channels.groupsAssociatedToChannel[channelID].ids;
|
||||
const expectedIDs = ['existing2'].concat(response.map((group) => group.id));
|
||||
assert.strictEqual(groupIDs.length, expectedIDs.length);
|
||||
groupIDs.forEach((id) => {
|
||||
assert.ok(expectedIDs.includes(id));
|
||||
});
|
||||
});
|
||||
|
||||
it('patchGroupSyncable', async () => {
|
||||
const groupID = '5rgoajywb3nfbdtyafbod47rya';
|
||||
const teamID = 'ge63nq31sbfy3duzq5f7yqn1kh';
|
||||
const channelID = 'o3tdawqxot8kikzq8bk54zggbc';
|
||||
|
||||
const groupSyncablePatch = {
|
||||
auto_add: true,
|
||||
scheme_admin: true,
|
||||
};
|
||||
|
||||
const groupTeamResponse = {
|
||||
team_id: 'ge63nq31sbfy3duzq5f7yqn1kh',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
scheme_admin: true,
|
||||
create_at: 1542643748412,
|
||||
delete_at: 0,
|
||||
update_at: 1542660566032,
|
||||
};
|
||||
|
||||
const groupChannelResponse = {
|
||||
channel_id: 'o3tdawqxot8kikzq8bk54zggbc',
|
||||
group_id: '5rgoajywb3nfbdtyafbod47rya',
|
||||
auto_add: true,
|
||||
scheme_admin: true,
|
||||
create_at: 1542644105041,
|
||||
delete_at: 0,
|
||||
update_at: 1542662607342,
|
||||
};
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
put(`/groups/${groupID}/teams/${teamID}/patch`).
|
||||
reply(200, groupTeamResponse);
|
||||
|
||||
nock(Client4.getBaseRoute()).
|
||||
put(`/groups/${groupID}/channels/${channelID}/patch`).
|
||||
reply(200, groupChannelResponse);
|
||||
|
||||
await Actions.patchGroupSyncable(groupID, teamID, Groups.SYNCABLE_TYPE_TEAM, groupSyncablePatch)(store.dispatch, store.getState);
|
||||
await Actions.patchGroupSyncable(groupID, channelID, Groups.SYNCABLE_TYPE_CHANNEL, groupSyncablePatch)(store.dispatch, store.getState);
|
||||
|
||||
const state = store.getState();
|
||||
const groupSyncables = state.entities.groups.syncables[groupID];
|
||||
assert.ok(groupSyncables);
|
||||
|
||||
assert.ok(groupSyncables.teams[0].auto_add === groupSyncablePatch.auto_add);
|
||||
assert.ok(groupSyncables.channels[0].auto_add === groupSyncablePatch.auto_add);
|
||||
|
||||
assert.ok(groupSyncables.teams[0].scheme_admin === groupSyncablePatch.scheme_admin);
|
||||
assert.ok(groupSyncables.channels[0].scheme_admin === groupSyncablePatch.scheme_admin);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {GroupTypes} from '@mm-redux/action_types';
|
||||
import {General, Groups} from '../constants';
|
||||
import {Client4} from '@mm-redux/client';
|
||||
|
||||
import {Action, ActionFunc, batchActions, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {SyncableType, SyncablePatch} from '@mm-redux/types/groups';
|
||||
|
||||
import {logError} from './errors';
|
||||
import {bindClientFunc, forceLogoutIfNecessary} from './helpers';
|
||||
|
||||
export function linkGroupSyncable(groupID: string, syncableID: string, syncableType: SyncableType, patch: SyncablePatch): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
let data;
|
||||
try {
|
||||
data = await Client4.linkGroupSyncable(groupID, syncableID, syncableType, patch);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const dispatches: Action[] = [];
|
||||
|
||||
let type = '';
|
||||
switch (syncableType) {
|
||||
case Groups.SYNCABLE_TYPE_TEAM:
|
||||
dispatches.push({type: GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_TEAM, data: {teamID: syncableID, groups: [{id: groupID}]}});
|
||||
type = GroupTypes.LINKED_GROUP_TEAM;
|
||||
break;
|
||||
case Groups.SYNCABLE_TYPE_CHANNEL:
|
||||
type = GroupTypes.LINKED_GROUP_CHANNEL;
|
||||
break;
|
||||
default:
|
||||
console.warn(`unhandled syncable type ${syncableType}`); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
dispatches.push({type, data});
|
||||
dispatch(batchActions(dispatches));
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function unlinkGroupSyncable(groupID: string, syncableID: string, syncableType: SyncableType): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
try {
|
||||
await Client4.unlinkGroupSyncable(groupID, syncableID, syncableType);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
const dispatches: Action[] = [];
|
||||
|
||||
let type = '';
|
||||
const data = {group_id: groupID, syncable_id: syncableID};
|
||||
switch (syncableType) {
|
||||
case Groups.SYNCABLE_TYPE_TEAM:
|
||||
type = GroupTypes.UNLINKED_GROUP_TEAM;
|
||||
data.syncable_id = syncableID;
|
||||
dispatches.push({type: GroupTypes.RECEIVED_GROUPS_NOT_ASSOCIATED_TO_TEAM, data: {teamID: syncableID, groups: [{id: groupID}]}});
|
||||
break;
|
||||
case Groups.SYNCABLE_TYPE_CHANNEL:
|
||||
type = GroupTypes.UNLINKED_GROUP_CHANNEL;
|
||||
data.syncable_id = syncableID;
|
||||
break;
|
||||
default:
|
||||
console.warn(`unhandled syncable type ${syncableType}`); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
dispatches.push({type, data});
|
||||
dispatch(batchActions(dispatches));
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function getGroupSyncables(groupID: string, syncableType: SyncableType): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
let data;
|
||||
try {
|
||||
data = await Client4.getGroupSyncables(groupID, syncableType);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
let type = '';
|
||||
switch (syncableType) {
|
||||
case Groups.SYNCABLE_TYPE_TEAM:
|
||||
type = GroupTypes.RECEIVED_GROUP_TEAMS;
|
||||
break;
|
||||
case Groups.SYNCABLE_TYPE_CHANNEL:
|
||||
type = GroupTypes.RECEIVED_GROUP_CHANNELS;
|
||||
break;
|
||||
default:
|
||||
console.warn(`unhandled syncable type ${syncableType}`); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
{type, data, group_id: groupID},
|
||||
]));
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function patchGroupSyncable(groupID: string, syncableID: string, syncableType: SyncableType, patch: SyncablePatch): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
let data;
|
||||
try {
|
||||
data = await Client4.patchGroupSyncable(groupID, syncableID, syncableType, patch);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
return {error};
|
||||
}
|
||||
|
||||
const dispatches: Action[] = [];
|
||||
|
||||
let type = '';
|
||||
switch (syncableType) {
|
||||
case Groups.SYNCABLE_TYPE_TEAM:
|
||||
type = GroupTypes.PATCHED_GROUP_TEAM;
|
||||
break;
|
||||
case Groups.SYNCABLE_TYPE_CHANNEL:
|
||||
type = GroupTypes.PATCHED_GROUP_CHANNEL;
|
||||
break;
|
||||
default:
|
||||
console.warn(`unhandled syncable type ${syncableType}`); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
dispatches.push(
|
||||
{type, data},
|
||||
);
|
||||
dispatch(batchActions(dispatches));
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function getGroupMembers(groupID: string, page = 0, perPage: number = General.PAGE_SIZE_DEFAULT): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
let data;
|
||||
try {
|
||||
data = await Client4.getGroupMembers(groupID, page, perPage);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
return {error};
|
||||
}
|
||||
|
||||
dispatch(batchActions([
|
||||
{type: GroupTypes.RECEIVED_GROUP_MEMBERS, group_id: groupID, data},
|
||||
]));
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function getGroup(id: string): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroup,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUP],
|
||||
params: [
|
||||
id,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToTeam,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
params: [
|
||||
teamID,
|
||||
q,
|
||||
page,
|
||||
perPage,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsNotAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: Client4.getGroupsNotAssociatedToChannel,
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS],
|
||||
params: [
|
||||
channelID,
|
||||
q,
|
||||
page,
|
||||
perPage,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getAllGroupsAssociatedToTeam(teamID: string): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: async (param1) => {
|
||||
const result = await Client4.getAllGroupsAssociatedToTeam(param1);
|
||||
result.teamID = param1;
|
||||
return result;
|
||||
},
|
||||
onSuccess: [GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_TEAM],
|
||||
params: [
|
||||
teamID,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getAllGroupsAssociatedToChannel(channelID: string): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: async (param1) => {
|
||||
const result = await Client4.getAllGroupsAssociatedToChannel(param1);
|
||||
result.channelID = param1;
|
||||
return result;
|
||||
},
|
||||
onSuccess: [GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNEL],
|
||||
params: [
|
||||
channelID,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsAssociatedToTeam(teamID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: async (param1, param2, param3, param4) => {
|
||||
const result = await Client4.getGroupsAssociatedToTeam(param1, param2, param3, param4);
|
||||
return {groups: result.groups, totalGroupCount: result.total_group_count, teamID: param1};
|
||||
},
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_TEAM],
|
||||
params: [
|
||||
teamID,
|
||||
q,
|
||||
page,
|
||||
perPage,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function getGroupsAssociatedToChannel(channelID: string, q = '', page = 0, perPage: number = General.PAGE_SIZE_DEFAULT): ActionFunc {
|
||||
return bindClientFunc({
|
||||
clientFunc: async (param1, param2, param3, param4) => {
|
||||
const result = await Client4.getGroupsAssociatedToChannel(param1, param2, param3, param4);
|
||||
return {groups: result.groups, totalGroupCount: result.total_group_count, channelID: param1};
|
||||
},
|
||||
onSuccess: [GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_CHANNEL],
|
||||
params: [
|
||||
channelID,
|
||||
q,
|
||||
page,
|
||||
perPage,
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import * as errors from './errors';
|
||||
import * as emojis from './emojis';
|
||||
import * as files from './files';
|
||||
import * as general from './general';
|
||||
import * as groups from './groups';
|
||||
import * as gifs from './gifs';
|
||||
import * as helpers from './helpers';
|
||||
import * as integrations from './integrations';
|
||||
@@ -28,7 +27,6 @@ export {
|
||||
emojis,
|
||||
files,
|
||||
general,
|
||||
groups,
|
||||
gifs,
|
||||
integrations,
|
||||
helpers,
|
||||
|
||||
@@ -2847,6 +2847,13 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
getGroups = async (filterAllowReference = false) => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/groups${buildQueryString({filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsNotAssociatedToTeam = async (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
this.trackEvent('api', 'api_groups_get_not_associated_to_team', {team_id: teamID});
|
||||
return this.doFetch(
|
||||
@@ -2863,34 +2870,41 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsAssociatedToTeam = async (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
getGroupsAssociatedToTeam = async (teamID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterAllowReference = false) => {
|
||||
this.trackEvent('api', 'api_groups_get_associated_to_team', {team_id: teamID});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups${buildQueryString({page, per_page: perPage, q, include_member_count: true})}`,
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups${buildQueryString({page, per_page: perPage, q, include_member_count: true, filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getGroupsAssociatedToChannel = async (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT) => {
|
||||
getGroupsAssociatedToChannel = async (channelID: string, q = '', page = 0, perPage = PER_PAGE_DEFAULT, filterAllowReference = false) => {
|
||||
this.trackEvent('api', 'api_groups_get_associated_to_channel', {channel_id: channelID});
|
||||
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/channels/${channelID}/groups${buildQueryString({page, per_page: perPage, q, include_member_count: true})}`,
|
||||
`${this.getBaseRoute()}/channels/${channelID}/groups${buildQueryString({page, per_page: perPage, q, include_member_count: true, filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getAllGroupsAssociatedToTeam = async (teamID: string) => {
|
||||
getAllGroupsAssociatedToTeam = async (teamID: string, filterAllowReference = false) => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups?paginate=false`,
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups${buildQueryString({paginate: false, filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getAllGroupsAssociatedToChannel = async (channelID: string) => {
|
||||
getAllGroupsAssociatedToChannelsInTeam = async (teamID: string, filterAllowReference = false) => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/channels/${channelID}/groups?paginate=false`,
|
||||
`${this.getBaseRoute()}/teams/${teamID}/groups_by_channels${buildQueryString({paginate: false, filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
getAllGroupsAssociatedToChannel = async (channelID: string, filterAllowReference = false) => {
|
||||
return this.doFetch(
|
||||
`${this.getBaseRoute()}/channels/${channelID}/groups${buildQueryString({paginate: false, filter_allow_reference: filterAllowReference})}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -547,6 +547,22 @@ function stats(state: RelationOneToOne<Channel, ChannelStats> = {}, action: Gene
|
||||
|
||||
function groupsAssociatedToChannel(state: any = {}, action: GenericAction) {
|
||||
switch (action.type) {
|
||||
case GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM: {
|
||||
const {groupsByChannelId} = action.data;
|
||||
const nextState = {...state};
|
||||
|
||||
for (const channelID of Object.keys(groupsByChannelId)) {
|
||||
if (groupsByChannelId[channelID]) {
|
||||
const associatedGroupIDs = new Set<string>([]);
|
||||
for (const group of groupsByChannelId[channelID]) {
|
||||
associatedGroupIDs.add(group.id);
|
||||
}
|
||||
const ids = Array.from(associatedGroupIDs);
|
||||
nextState[channelID] = {ids, totalCount: ids.length};
|
||||
}
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
case GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_CHANNEL: {
|
||||
const {channelID, groups, totalGroupCount} = action.data;
|
||||
const nextState = {...state};
|
||||
|
||||
@@ -194,7 +194,20 @@ function groups(state: Dictionary<Group> = {}, action: GenericAction) {
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
case GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNELS_IN_TEAM: {
|
||||
const nextState = {...state};
|
||||
const {groupsByChannelId} = action.data;
|
||||
|
||||
for (const group of Object.values(groupsByChannelId) as Group[]) {
|
||||
if (group) {
|
||||
nextState[group.id] = group;
|
||||
}
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
case GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_TEAM:
|
||||
case GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_TEAM:
|
||||
case GroupTypes.RECEIVED_ALL_GROUPS_ASSOCIATED_TO_CHANNEL:
|
||||
case GroupTypes.RECEIVED_GROUPS_ASSOCIATED_TO_CHANNEL: {
|
||||
const nextState = {...state};
|
||||
for (const group of action.data.groups) {
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('Selectors.Groups', () => {
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
allow_reference: true,
|
||||
},
|
||||
[expectedAssociatedGroupID3]: {
|
||||
id: expectedAssociatedGroupID3,
|
||||
@@ -47,9 +48,10 @@ describe('Selectors.Groups', () => {
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 5,
|
||||
allow_reference: false,
|
||||
},
|
||||
[expectedAssociatedGroupID4]: {
|
||||
id: [expectedAssociatedGroupID4],
|
||||
id: expectedAssociatedGroupID4,
|
||||
name: 'nobctj4brfgtpj3a1peiyq47tc',
|
||||
display_name: 'engineering',
|
||||
description: '',
|
||||
@@ -60,6 +62,7 @@ describe('Selectors.Groups', () => {
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 8,
|
||||
allow_reference: true,
|
||||
},
|
||||
[expectedAssociatedGroupID2]: {
|
||||
id: expectedAssociatedGroupID2,
|
||||
@@ -73,6 +76,7 @@ describe('Selectors.Groups', () => {
|
||||
delete_at: 0,
|
||||
has_syncables: false,
|
||||
member_count: 2,
|
||||
allow_reference: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -114,4 +118,26 @@ describe('Selectors.Groups', () => {
|
||||
const expected = Object.entries(testState.entities.groups.groups).filter(([groupID]) => !channelAssociatedGroupIDs.includes(groupID)).map(([, group]) => group);
|
||||
assert.deepEqual(Selectors.getGroupsNotAssociatedToChannel(testState, channelID), expected);
|
||||
});
|
||||
|
||||
it('getGroupsAssociatedToTeamForReference', () => {
|
||||
const expected = [
|
||||
testState.entities.groups.groups[expectedAssociatedGroupID1],
|
||||
];
|
||||
assert.deepEqual(Selectors.getGroupsAssociatedToTeamForReference(testState, teamID), expected);
|
||||
});
|
||||
|
||||
it('getGroupsAssociatedToChannelForReference', () => {
|
||||
const expected = [
|
||||
testState.entities.groups.groups[expectedAssociatedGroupID4],
|
||||
];
|
||||
assert.deepEqual(Selectors.getGroupsAssociatedToChannelForReference(testState, channelID), expected);
|
||||
});
|
||||
|
||||
it('getAllAssociatedGroupsForReference', () => {
|
||||
const expected = [
|
||||
testState.entities.groups.groups[expectedAssociatedGroupID1],
|
||||
testState.entities.groups.groups[expectedAssociatedGroupID4],
|
||||
];
|
||||
assert.deepEqual(Selectors.getAllAssociatedGroupsForReference(testState, channelID), expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import * as reselect from 'reselect';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Group} from '@mm-redux/types/groups';
|
||||
import {filterGroupsMatchingTerm} from '@mm-redux/utils/group_utils';
|
||||
import {getCurrentUserLocale} from '@mm-redux/selectors/entities/i18n';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {haveIChannelPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {getTeam} from '@mm-redux/selectors/entities/teams';
|
||||
import {Permissions} from '@mm-redux/constants';
|
||||
|
||||
const emptyList: any[] = [];
|
||||
const emptySyncables = {
|
||||
teams: [],
|
||||
@@ -9,7 +17,7 @@ const emptySyncables = {
|
||||
};
|
||||
|
||||
export function getAllGroups(state: GlobalState) {
|
||||
return state.entities.groups.groups;
|
||||
return state.entities.groups?.groups || [];
|
||||
}
|
||||
|
||||
export function getGroup(state: GlobalState, id: string) {
|
||||
@@ -45,6 +53,44 @@ export function getGroupMembers(state: GlobalState, id: string) {
|
||||
return groupMemberData.members;
|
||||
}
|
||||
|
||||
export function searchAssociatedGroupsForReferenceLocal(state: GlobalState, term: string, teamId: string, channelId: string): Array<Group> {
|
||||
const groups = getAssociatedGroupsForReference(state, teamId, channelId);
|
||||
if (!groups || groups.length === 0) {
|
||||
return emptyList;
|
||||
}
|
||||
const filteredGroups = filterGroupsMatchingTerm(groups, term);
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
export function getAssociatedGroupsForReference(state: GlobalState, teamId: string, channelId: string): Array<Group> {
|
||||
const team = getTeam(state, teamId);
|
||||
const channel = getChannel(state, channelId);
|
||||
const locale = getCurrentUserLocale(state);
|
||||
|
||||
if (!haveIChannelPermission(state, {
|
||||
permission: Permissions.USE_GROUP_MENTIONS,
|
||||
channel: channelId,
|
||||
team: teamId,
|
||||
default: true,
|
||||
})) {
|
||||
return emptyList;
|
||||
}
|
||||
|
||||
let groupsForReference = [];
|
||||
if (team && team.group_constrained && channel && channel.group_constrained) {
|
||||
const groupsFromChannel = getGroupsAssociatedToChannelForReference(state, channelId);
|
||||
const groupsFromTeam = getGroupsAssociatedToTeamForReference(state, teamId);
|
||||
groupsForReference = groupsFromChannel.concat(groupsFromTeam.filter((item) => groupsFromChannel.indexOf(item) < 0));
|
||||
} else if (team && team.group_constrained) {
|
||||
groupsForReference = getGroupsAssociatedToTeamForReference(state, teamId);
|
||||
} else if (channel && channel.group_constrained) {
|
||||
groupsForReference = getGroupsAssociatedToChannelForReference(state, channelId);
|
||||
} else {
|
||||
groupsForReference = getAllAssociatedGroupsForReference(state);
|
||||
}
|
||||
return groupsForReference.sort((groupA: Group, groupB: Group) => groupA.name.localeCompare(groupB.name, locale));
|
||||
}
|
||||
|
||||
const teamGroupIDs = (state: GlobalState, teamID: string) => state.entities.teams.groupsAssociatedToTeam[teamID]?.ids || [];
|
||||
|
||||
const channelGroupIDs = (state: GlobalState, channelID: string) => state.entities.channels.groupsAssociatedToChannel[channelID]?.ids || [];
|
||||
@@ -63,7 +109,7 @@ export const getGroupsNotAssociatedToTeam = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => !teamGroupIDSet.has(groupID)).map((entry) => entry[1]);
|
||||
return Object.values(allGroups).filter((group) => !teamGroupIDSet.has(group.id));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -71,7 +117,7 @@ export const getGroupsAssociatedToTeam = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => teamGroupIDSet.has(groupID)).map((entry) => entry[1]);
|
||||
return Object.values(allGroups).filter((group) => teamGroupIDSet.has(group.id));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -79,7 +125,7 @@ export const getGroupsNotAssociatedToChannel = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, channelID: string) => getChannelGroupIDSet(state, channelID),
|
||||
(allGroups, channelGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => !channelGroupIDSet.has(groupID)).map((entry) => entry[1]);
|
||||
return Object.values(allGroups).filter((group) => !channelGroupIDSet.has(group.id));
|
||||
},
|
||||
);
|
||||
|
||||
@@ -87,6 +133,29 @@ export const getGroupsAssociatedToChannel = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, channelID: string) => getChannelGroupIDSet(state, channelID),
|
||||
(allGroups, channelGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => channelGroupIDSet.has(groupID)).map((entry) => entry[1]);
|
||||
return Object.values(allGroups).filter((group) => channelGroupIDSet.has(group.id));
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsAssociatedToTeamForReference = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.values(allGroups).filter((group) => teamGroupIDSet.has(group.id) && group.allow_reference && group.delete_at === 0);
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsAssociatedToChannelForReference = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(state: GlobalState, channelID: string) => getChannelGroupIDSet(state, channelID),
|
||||
(allGroups, channelGroupIDSet) => {
|
||||
return Object.values(allGroups).filter((group) => channelGroupIDSet.has(group.id) && group.allow_reference && group.delete_at === 0);
|
||||
},
|
||||
);
|
||||
|
||||
export const getAllAssociatedGroupsForReference = reselect.createSelector(
|
||||
getAllGroups,
|
||||
(allGroups) => {
|
||||
return Object.values(allGroups).filter((group) => group.allow_reference && group.delete_at === 0);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ export type Group = {
|
||||
has_syncables: boolean;
|
||||
member_count: number;
|
||||
scheme_admin: boolean;
|
||||
allow_reference: boolean;
|
||||
};
|
||||
export type GroupTeam = {
|
||||
team_id: string;
|
||||
|
||||
92
app/mm-redux/utils/group_utils.test.js
Normal file
92
app/mm-redux/utils/group_utils.test.js
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {
|
||||
filterGroupsMatchingTerm,
|
||||
} from './group_utils';
|
||||
|
||||
describe('group utils', () => {
|
||||
describe('filterGroupsMatchingTerm', () => {
|
||||
const groupA = {
|
||||
id: 'groupid1',
|
||||
name: 'board-group',
|
||||
description: 'group1 description',
|
||||
display_name: 'board-group',
|
||||
type: 'ldap',
|
||||
remote_id: 'group1',
|
||||
create_at: 1,
|
||||
update_at: 2,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
member_count: 3,
|
||||
scheme_admin: false,
|
||||
allow_reference: true,
|
||||
};
|
||||
const groupB = {
|
||||
id: 'groupid2',
|
||||
name: 'developers-group',
|
||||
description: 'group2 description',
|
||||
display_name: 'developers-group',
|
||||
type: 'ldap',
|
||||
remote_id: 'group2',
|
||||
create_at: 1,
|
||||
update_at: 2,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
member_count: 3,
|
||||
scheme_admin: false,
|
||||
allow_reference: true,
|
||||
};
|
||||
const groupC = {
|
||||
id: 'groupid3',
|
||||
name: 'software-engineers',
|
||||
description: 'group3 description',
|
||||
display_name: 'software engineers',
|
||||
type: 'ldap',
|
||||
remote_id: 'group3',
|
||||
create_at: 1,
|
||||
update_at: 2,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
member_count: 3,
|
||||
scheme_admin: false,
|
||||
allow_reference: true,
|
||||
};
|
||||
const groups = [groupA, groupB, groupC];
|
||||
|
||||
it('should match all for empty filter', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, ''), [groupA, groupB, groupC]);
|
||||
});
|
||||
|
||||
it('should filter out results which do not match', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'testBad'), []);
|
||||
});
|
||||
|
||||
it('should match by name', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'software-engineers'), [groupC]);
|
||||
});
|
||||
|
||||
it('should match by split part of the name', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'group'), [groupA, groupB]);
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'board'), [groupA]);
|
||||
});
|
||||
|
||||
it('should match by display_name fully', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'software engineers'), [groupC]);
|
||||
});
|
||||
|
||||
it('should match by display_name case-insensitive', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, 'software ENGINEERS'), [groupC]);
|
||||
});
|
||||
|
||||
it('should ignore leading @ for name', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, '@developers'), [groupB]);
|
||||
});
|
||||
|
||||
it('should ignore leading @ for display_name', () => {
|
||||
assert.deepEqual(filterGroupsMatchingTerm(groups, '@software'), [groupC]);
|
||||
});
|
||||
});
|
||||
});
|
||||
35
app/mm-redux/utils/group_utils.ts
Normal file
35
app/mm-redux/utils/group_utils.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {General} from '../constants';
|
||||
import {Group} from '@mm-redux/types/groups';
|
||||
import {getSuggestionsSplitByMultiple} from './user_utils';
|
||||
|
||||
export function filterGroupsMatchingTerm(groups: Array<Group>, term: string): Array<Group> {
|
||||
const lowercasedTerm = term.toLowerCase();
|
||||
let trimmedTerm = lowercasedTerm;
|
||||
|
||||
if (trimmedTerm.startsWith('@')) {
|
||||
trimmedTerm = trimmedTerm.substr(1);
|
||||
}
|
||||
|
||||
if (!trimmedTerm) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
return groups.filter((group: Group) => {
|
||||
if (!group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const groupSuggestions: string[] = [];
|
||||
|
||||
const groupnameSuggestions = getSuggestionsSplitByMultiple((group.name || '').toLowerCase(), General.AUTOCOMPLETE_SPLIT_CHARACTERS);
|
||||
|
||||
groupSuggestions.push(...groupnameSuggestions);
|
||||
const displayname = (group.display_name || '').toLowerCase();
|
||||
groupSuggestions.push(displayname);
|
||||
|
||||
return groupSuggestions.
|
||||
some((suggestion) => suggestion.startsWith(trimmedTerm));
|
||||
});
|
||||
}
|
||||
@@ -571,6 +571,7 @@
|
||||
"suggestion.mention.all": "Notifies everyone in this channel",
|
||||
"suggestion.mention.channel": "Notifies everyone in this channel",
|
||||
"suggestion.mention.channels": "My Channels",
|
||||
"suggestion.mention.groups": "Group Mentions",
|
||||
"suggestion.mention.here": "Notifies everyone online in this channel",
|
||||
"suggestion.mention.members": "Channel Members",
|
||||
"suggestion.mention.morechannels": "Other Channels",
|
||||
|
||||
Reference in New Issue
Block a user