* Show Confirmation Dialogue WIP First Commit Show Confirmation Dialogue WIP Second Commit refactoring according to comments refactor code according to comments Fix linting problems add i18n strings Update regex pattern add test and make fixes fix message not submitting Fix linting fix index.js fix conflicts address PR comments address PR comments single dispatch Address PR comments add test * Show Confirmation Dialogue WIP First Commit Show Confirmation Dialogue WIP Second Commit refactoring according to comments refactor code according to comments Fix linting problems add i18n strings Update regex pattern add test and make fixes fix message not submitting Fix linting fix index.js fix conflicts address PR comments address PR comments single dispatch Address PR comments add test * make some changes * fix test failures * Address PR comments * Update app/mm-redux/types/channels.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Update app/mm-redux/selectors/entities/channels.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Update app/mm-redux/selectors/entities/channels.test.js Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Update app/constants/autocomplete.js Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Address PR comments * make group mention mapping its own function * Address PR comments * Update app/components/post_draft/post_draft.js Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Merge branch 'master' of https://github.com/mattermost/mattermost-mobile into MM-23785-ShowConfirmationDialogue-2 * Merge branch 'master' into MM-23785-ShowConfirmationDialogue-2 * Address MM-26987 * Retrieve group information on mount RN: Group Mention confirmation prompt not shown on default channel load * Update Regex to fix MM-26976 Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
7c41c83196
commit
fc815adaeb
16 changed files with 828 additions and 21 deletions
|
|
@ -7,12 +7,13 @@ import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
|
|||
import {General, Permissions} from '@mm-redux/constants';
|
||||
import {createPost} from '@mm-redux/actions/posts';
|
||||
import {setStatus} from '@mm-redux/actions/users';
|
||||
import {getCurrentChannel, isCurrentChannelReadOnly, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentChannel, isCurrentChannelReadOnly, getCurrentChannelStats, getChannelMemberCountsByGroup as selectChannelMemberCountsByGroup} from '@mm-redux/selectors/entities/channels';
|
||||
import {haveIChannelPermission} from '@mm-redux/selectors/entities/roles';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getConfig, getLicense} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getStatusForUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {getChannelTimezones} from '@mm-redux/actions/channels';
|
||||
import {getChannelTimezones, getChannelMemberCountsByGroup} from '@mm-redux/actions/channels';
|
||||
import {getAssociatedGroupsForReferenceMap} from '@mm-redux/selectors/entities/groups';
|
||||
|
||||
import {executeCommand} from '@actions/views/command';
|
||||
import {addReactionToLatestPost} from '@actions/views/emoji';
|
||||
|
|
@ -36,8 +37,16 @@ export function mapStateToProps(state, ownProps) {
|
|||
const currentChannelStats = getCurrentChannelStats(state);
|
||||
const membersCount = currentChannelStats?.member_count || 0; // eslint-disable-line camelcase
|
||||
const isTimezoneEnabled = config?.ExperimentalTimezone === 'true';
|
||||
|
||||
const channelId = ownProps.channelId || (currentChannel ? currentChannel.id : '');
|
||||
const channelTeamId = currentChannel ? currentChannel.team_id : '';
|
||||
const license = getLicense(state);
|
||||
let canPost = true;
|
||||
let useChannelMentions = true;
|
||||
let deactivatedChannel = false;
|
||||
let useGroupMentions = false;
|
||||
const channelMemberCountsByGroup = selectChannelMemberCountsByGroup(state, channelId);
|
||||
let groupsWithAllowReference = new Map();
|
||||
|
||||
if (currentChannel && currentChannel.type === General.DM_CHANNEL) {
|
||||
const teammate = getChannelMembersForDm(state, currentChannel);
|
||||
if (teammate.length && teammate[0].delete_at) {
|
||||
|
|
@ -45,8 +54,6 @@ export function mapStateToProps(state, ownProps) {
|
|||
}
|
||||
}
|
||||
|
||||
let canPost = true;
|
||||
let useChannelMentions = true;
|
||||
if (currentChannel && isMinimumServerVersion(state.entities.general.serverVersion, 5, 22)) {
|
||||
canPost = haveIChannelPermission(
|
||||
state,
|
||||
|
|
@ -68,7 +75,20 @@ export function mapStateToProps(state, ownProps) {
|
|||
);
|
||||
}
|
||||
|
||||
const channelId = ownProps.channelId || (currentChannel ? currentChannel.id : '');
|
||||
if (isMinimumServerVersion(state.entities.general.serverVersion, 5, 24) && license && license.IsLicensed === 'true') {
|
||||
useGroupMentions = haveIChannelPermission(
|
||||
state,
|
||||
{
|
||||
channel: currentChannel.id,
|
||||
team: currentChannel.team_id,
|
||||
permission: Permissions.USE_GROUP_MENTIONS,
|
||||
},
|
||||
);
|
||||
|
||||
if (useGroupMentions) {
|
||||
groupsWithAllowReference = getAssociatedGroupsForReferenceMap(state, channelTeamId, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
let channelIsReadOnly = false;
|
||||
if (currentUserId && channelId) {
|
||||
|
|
@ -77,8 +97,10 @@ export function mapStateToProps(state, ownProps) {
|
|||
|
||||
return {
|
||||
canPost,
|
||||
channelDisplayName: state.views.channel.displayName || (currentChannel ? currentChannel.display_name : ''),
|
||||
currentChannel,
|
||||
channelId,
|
||||
channelTeamId,
|
||||
channelDisplayName: state.views.channel.displayName || (currentChannel ? currentChannel.display_name : ''),
|
||||
channelIsArchived: ownProps.channelIsArchived || (currentChannel ? currentChannel.delete_at !== 0 : false),
|
||||
channelIsReadOnly,
|
||||
currentUserId,
|
||||
|
|
@ -94,6 +116,9 @@ export function mapStateToProps(state, ownProps) {
|
|||
useChannelMentions,
|
||||
userIsOutOfOffice,
|
||||
value: currentDraft.draft,
|
||||
groupsWithAllowReference,
|
||||
useGroupMentions,
|
||||
channelMemberCountsByGroup,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +131,7 @@ const mapDispatchToProps = {
|
|||
handleClearFailedFiles,
|
||||
initUploadFiles,
|
||||
setStatus,
|
||||
getChannelMemberCountsByGroup,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {forwardRef: true})(PostDraft);
|
||||
|
|
|
|||
|
|
@ -37,14 +37,20 @@ describe('mapStateToProps', () => {
|
|||
serverVersion: '',
|
||||
},
|
||||
users: {
|
||||
profiles: {},
|
||||
currentUserId: '',
|
||||
},
|
||||
channels: {
|
||||
currentChannelId: '',
|
||||
channelMemberCountsByGroup: {},
|
||||
channels: {},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
teams: {
|
||||
teams: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
channel: {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Autocomplete from '@components/autocomplete';
|
|||
import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing';
|
||||
import {CHANNEL_POST_TEXTBOX_CURSOR_CHANGE, CHANNEL_POST_TEXTBOX_VALUE_CHANGE, IS_REACTION_REGEX, MAX_FILE_COUNT} from '@constants/post_draft';
|
||||
import {NOTIFY_ALL_MEMBERS} from '@constants/view';
|
||||
import {AT_MENTION_REGEX_GLOBAL, CODE_REGEX} from 'app/constants/autocomplete';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {getFormattedFileSize} from '@mm-redux/utils/file_utils';
|
||||
|
|
@ -31,6 +32,7 @@ export default class PostDraft extends PureComponent {
|
|||
static propTypes = {
|
||||
registerTypingAnimation: PropTypes.func.isRequired,
|
||||
addReactionToLatestPost: PropTypes.func.isRequired,
|
||||
getChannelMemberCountsByGroup: PropTypes.func.isRequired,
|
||||
canPost: PropTypes.bool.isRequired,
|
||||
channelDisplayName: PropTypes.string,
|
||||
channelId: PropTypes.string.isRequired,
|
||||
|
|
@ -60,6 +62,9 @@ export default class PostDraft extends PureComponent {
|
|||
userIsOutOfOffice: PropTypes.bool.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
valueEvent: PropTypes.string,
|
||||
useGroupMentions: PropTypes.bool.isRequired,
|
||||
channelMemberCountsByGroup: PropTypes.object,
|
||||
groupsWithAllowReference: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -89,24 +94,33 @@ export default class PostDraft extends PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
componentDidMount(prevProps) {
|
||||
if (this.props.isTimezoneEnabled !== prevProps?.isTimezoneEnabled || prevProps?.channelId !== this.props.channelId) {
|
||||
this.numberOfTimezones().then((channelTimezoneCount) => this.setState({channelTimezoneCount}));
|
||||
componentDidMount() {
|
||||
const {getChannelMemberCountsByGroup, channelId, isTimezoneEnabled, useGroupMentions} = this.props;
|
||||
if (useGroupMentions) {
|
||||
getChannelMemberCountsByGroup(channelId, isTimezoneEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.input.current) {
|
||||
const {channelId, rootId, value} = this.props;
|
||||
const diffChannel = channelId !== prevProps.channelId;
|
||||
const diffThread = rootId !== prevProps.rootId;
|
||||
const {channelId, rootId, value, useGroupMentions, getChannelMemberCountsByGroup, isTimezoneEnabled} = this.props;
|
||||
const diffChannel = channelId !== prevProps?.channelId;
|
||||
const diffTimezoneEnabled = isTimezoneEnabled !== prevProps?.isTimezoneEnabled;
|
||||
|
||||
if (this.input.current) {
|
||||
const diffThread = rootId !== prevProps.rootId;
|
||||
if (diffChannel || diffThread) {
|
||||
const trimmed = value.trim();
|
||||
this.input.current.setValue(trimmed);
|
||||
this.updateInitialValue(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
if (diffTimezoneEnabled || diffChannel) {
|
||||
this.numberOfTimezones().then((channelTimezoneCount) => this.setState({channelTimezoneCount}));
|
||||
if (useGroupMentions) {
|
||||
getChannelMemberCountsByGroup(channelId, isTimezoneEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blurTextBox = () => {
|
||||
|
|
@ -132,6 +146,98 @@ export default class PostDraft extends PureComponent {
|
|||
return messageLength > 0;
|
||||
};
|
||||
|
||||
showSendToGroupsAlert = (groupMentions, memberNotifyCount, channelTimezoneCount, msg) => {
|
||||
const {intl} = this.context;
|
||||
|
||||
let notifyAllMessage = '';
|
||||
if (groupMentions.length === 1) {
|
||||
if (channelTimezoneCount > 0) {
|
||||
notifyAllMessage = (
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.post_textbox.one_group.message.with_timezones',
|
||||
defaultMessage: 'By using {mention} you are about to send notifications to {totalMembers} people in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?',
|
||||
},
|
||||
{
|
||||
mention: groupMentions[0],
|
||||
totalMembers: memberNotifyCount,
|
||||
timezones: channelTimezoneCount,
|
||||
},
|
||||
)
|
||||
);
|
||||
} else {
|
||||
notifyAllMessage = (
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.post_textbox.one_group.message.without_timezones',
|
||||
defaultMessage: 'By using {mention} you are about to send notifications to {totalMembers} people. Are you sure you want to do this?',
|
||||
},
|
||||
{
|
||||
mention: groupMentions[0],
|
||||
totalMembers: memberNotifyCount,
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (channelTimezoneCount > 0) {
|
||||
notifyAllMessage = (
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.post_textbox.multi_group.message.with_timezones',
|
||||
defaultMessage: 'By using {mentions} and {finalMention} you are about to send notifications to at least {totalMembers} people in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?',
|
||||
},
|
||||
{
|
||||
mentions: groupMentions.slice(0, -1).join(', '),
|
||||
finalMention: groupMentions[groupMentions.length - 1],
|
||||
totalMembers: memberNotifyCount,
|
||||
timezones: channelTimezoneCount,
|
||||
},
|
||||
)
|
||||
);
|
||||
} else {
|
||||
notifyAllMessage = (
|
||||
intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.post_textbox.multi_group.message.without_timezones',
|
||||
defaultMessage: 'By using {mentions} and {finalMention} you are about to send notifications to at least {totalMembers} people. Are you sure you want to do this?',
|
||||
},
|
||||
{
|
||||
mentions: groupMentions.slice(0, -1).join(', '),
|
||||
finalMention: groupMentions[groupMentions.length - 1],
|
||||
totalMembers: memberNotifyCount,
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.post_textbox.groups.title',
|
||||
defaultMessage: 'Confirm sending notifications to groups',
|
||||
}),
|
||||
notifyAllMessage,
|
||||
[
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'mobile.post_textbox.entire_channel.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
}),
|
||||
onPress: () => {
|
||||
this.input.current.setValue(msg);
|
||||
this.setState({sendingMessage: false});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({
|
||||
id: 'mobile.post_textbox.entire_channel.confirm',
|
||||
defaultMessage: 'Confirm',
|
||||
}),
|
||||
onPress: () => this.doSubmitMessage(),
|
||||
},
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
doSubmitMessage = () => {
|
||||
const {createPost, currentUserId, channelId, files, handleClearFiles, rootId} = this.props;
|
||||
const value = this.input.current.getValue();
|
||||
|
|
@ -362,18 +468,44 @@ export default class PostDraft extends PureComponent {
|
|||
this.input.current.changeDraft('');
|
||||
};
|
||||
|
||||
mapGroupMentions = (groupMentions) => {
|
||||
const {channelMemberCountsByGroup} = this.props;
|
||||
let memberNotifyCount = 0;
|
||||
let channelTimezoneCount = 0;
|
||||
const groupMentionsSet = new Set();
|
||||
groupMentions.
|
||||
forEach((group) => {
|
||||
const mappedValue = channelMemberCountsByGroup[group.id];
|
||||
if (mappedValue?.channel_member_count > NOTIFY_ALL_MEMBERS && mappedValue?.channel_member_count > memberNotifyCount) {
|
||||
memberNotifyCount = mappedValue.channel_member_count;
|
||||
channelTimezoneCount = mappedValue.channel_member_timezones_count;
|
||||
}
|
||||
groupMentionsSet.add(`@${group.name}`);
|
||||
});
|
||||
return {groupMentionsSet, memberNotifyCount, channelTimezoneCount};
|
||||
}
|
||||
|
||||
sendMessage = () => {
|
||||
const value = this.input.current.getValue();
|
||||
|
||||
if (value) {
|
||||
const {enableConfirmNotificationsToChannel, membersCount, useChannelMentions} = this.props;
|
||||
const {enableConfirmNotificationsToChannel, membersCount, useGroupMentions, useChannelMentions} = this.props;
|
||||
const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions;
|
||||
const notificationsToGroups = enableConfirmNotificationsToChannel && useGroupMentions;
|
||||
const toAllOrChannel = this.textContainsAtAllAtChannel(value);
|
||||
const groupMentions = (!toAllOrChannel && notificationsToGroups) ? this.groupsMentionedInText(value) : [];
|
||||
|
||||
if (value.indexOf('/') === 0) {
|
||||
this.sendCommand(value);
|
||||
} else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && toAllOrChannel) {
|
||||
this.showSendToAllOrChannelAlert(membersCount, value);
|
||||
} else if (groupMentions.length > 0) {
|
||||
const {groupMentionsSet, memberNotifyCount, channelTimezoneCount} = this.mapGroupMentions(groupMentions);
|
||||
if (memberNotifyCount > 0) {
|
||||
this.showSendToGroupsAlert(Array.from(groupMentionsSet), memberNotifyCount, channelTimezoneCount, value);
|
||||
} else {
|
||||
this.doSubmitMessage();
|
||||
}
|
||||
} else {
|
||||
this.doSubmitMessage();
|
||||
}
|
||||
|
|
@ -474,10 +606,26 @@ export default class PostDraft extends PureComponent {
|
|||
};
|
||||
|
||||
textContainsAtAllAtChannel = (text) => {
|
||||
const textWithoutCode = text.replace(/(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)| *(`{3,}|~{3,})[ .]*(\S+)? *\n([\s\S]*?\s*)\3 *(?:\n+|$)/g, '');
|
||||
return (/\B@(all|channel)\b/i).test(textWithoutCode);
|
||||
const textWithoutCode = text.replace(CODE_REGEX, '');
|
||||
return (/(?:\B|\b_+)@(channel|all)(?!(\.|-|_)*[^\W_])/i).test(textWithoutCode);
|
||||
};
|
||||
|
||||
groupsMentionedInText = (text) => {
|
||||
const {groupsWithAllowReference} = this.props;
|
||||
const groups = [];
|
||||
if (groupsWithAllowReference.size > 0) {
|
||||
const textWithoutCode = text.replace(CODE_REGEX, '');
|
||||
const mentions = textWithoutCode.match(AT_MENTION_REGEX_GLOBAL) || [];
|
||||
mentions.forEach((mention) => {
|
||||
const group = groupsWithAllowReference.get(mention);
|
||||
if (group) {
|
||||
groups.push(group);
|
||||
}
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
updateInitialValue = (value) => {
|
||||
this.setState({value});
|
||||
}
|
||||
|
|
|
|||
349
app/components/post_draft/post_draft.test.js
Normal file
349
app/components/post_draft/post_draft.test.js
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import assert from 'assert';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import PostDraft from './post_draft';
|
||||
|
||||
jest.mock('react-native-image-picker', () => ({
|
||||
launchCamera: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('PostDraft', () => {
|
||||
const baseProps = {
|
||||
addReactionToLatestPost: jest.fn(),
|
||||
createPost: jest.fn(),
|
||||
executeCommand: jest.fn(),
|
||||
handleCommentDraftChanged: jest.fn(),
|
||||
handlePostDraftChanged: jest.fn(),
|
||||
handleClearFiles: jest.fn(),
|
||||
handleClearFailedFiles: jest.fn(),
|
||||
handleRemoveLastFile: jest.fn(),
|
||||
initUploadFiles: jest.fn(),
|
||||
userTyping: jest.fn(),
|
||||
handleCommentDraftSelectionChanged: jest.fn(),
|
||||
setStatus: jest.fn(),
|
||||
selectPenultimateChannel: jest.fn(),
|
||||
getChannelTimezones: jest.fn(),
|
||||
getChannelMemberCountsByGroup: jest.fn(),
|
||||
canUploadFiles: true,
|
||||
channelId: 'channel-id',
|
||||
channelDisplayName: 'Test Channel',
|
||||
channelTeamId: 'channel-team-id',
|
||||
channelIsReadOnly: false,
|
||||
currentUserId: 'current-user-id',
|
||||
deactivatedChannel: false,
|
||||
files: [],
|
||||
maxFileSize: 1024,
|
||||
maxMessageLength: 4000,
|
||||
rootId: '',
|
||||
theme: Preferences.THEMES.default,
|
||||
uploadFileRequestStatus: 'NOT_STARTED',
|
||||
value: '',
|
||||
userIsOutOfOffice: false,
|
||||
channelIsArchived: false,
|
||||
onCloseChannel: jest.fn(),
|
||||
cursorPositionEvent: '',
|
||||
valueEvent: '',
|
||||
isLandscape: false,
|
||||
screenId: 'NavigationScreen1',
|
||||
canPost: true,
|
||||
currentChannelMembersCount: 50,
|
||||
enableConfirmNotificationsToChannel: true,
|
||||
useChannelMentions: true,
|
||||
useGroupMentions: true,
|
||||
groupsWithAllowReference: new Map([
|
||||
['@developers', {
|
||||
id: 'developers',
|
||||
name: 'developers',
|
||||
}],
|
||||
['@qa', {
|
||||
id: 'qa',
|
||||
name: 'qa',
|
||||
}],
|
||||
]),
|
||||
channelMemberCountsByGroup: {
|
||||
developers: {
|
||||
channel_member_count: 10,
|
||||
channel_member_timezones_count: 0,
|
||||
},
|
||||
qa: {
|
||||
channel_member_count: 3,
|
||||
channel_member_timezones_count: 0,
|
||||
},
|
||||
},
|
||||
membersCount: 10,
|
||||
};
|
||||
const ref = React.createRef();
|
||||
|
||||
test('should send an alert when sending a message with a channel mention', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft
|
||||
{...baseProps}
|
||||
ref={ref}
|
||||
/>,
|
||||
);
|
||||
const message = '@all';
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.input).toEqual({current: null});
|
||||
instance.input = {
|
||||
current: {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
expect(Alert.alert).toBeCalled();
|
||||
expect(Alert.alert).toHaveBeenCalledWith('Confirm sending notifications to entire channel', expect.anything(), expect.anything());
|
||||
});
|
||||
|
||||
test('should send an alert when sending a message with a group mention with group with count more than NOTIFY_ALL', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft
|
||||
{...baseProps}
|
||||
ref={ref}
|
||||
/>,
|
||||
);
|
||||
const message = '@developers';
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.input).toEqual({current: null});
|
||||
instance.input = {
|
||||
current: {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
},
|
||||
};
|
||||
instance.sendMessage();
|
||||
expect(Alert.alert).toBeCalled();
|
||||
});
|
||||
|
||||
test('should not send an alert when sending a message with a group mention with group with count less than NOTIFY_ALL', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft
|
||||
{...baseProps}
|
||||
ref={ref}
|
||||
/>,
|
||||
);
|
||||
const message = '@qa';
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.input).toEqual({current: null});
|
||||
instance.input = {
|
||||
current: {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
expect(Alert.alert).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('should not send an alert when sending a message with a channel mention when the user does not have channel mentions permission', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft
|
||||
{...baseProps}
|
||||
useChannelMentions={false}
|
||||
ref={ref}
|
||||
/>,
|
||||
);
|
||||
const message = '@all';
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.input).toEqual({current: null});
|
||||
instance.input = {
|
||||
current: {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not send an alert when sending a message with a channel mention when the user does not have group mentions permission', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft
|
||||
{...baseProps}
|
||||
useGroupMentions={false}
|
||||
ref={ref}
|
||||
/>,
|
||||
);
|
||||
const message = '@developer';
|
||||
const instance = wrapper.instance();
|
||||
expect(instance.input).toEqual({current: null});
|
||||
instance.input = {
|
||||
current: {
|
||||
getValue: () => message,
|
||||
setValue: jest.fn(),
|
||||
changeDraft: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
instance.sendMessage();
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should return correct @all (same for @channel)', () => {
|
||||
for (const data of [
|
||||
{
|
||||
text: '',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: 'all',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@allison',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@ALLISON',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@all123',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '123@all',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: 'hey@all',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: 'hey@all.com',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@all',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@ALL',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@all hey',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'hey @all',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'HEY @ALL',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'hey @all!',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'hey @all:+1:',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'hey @ALL:+1:',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '`@all`',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@someone `@all`',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '``@all``',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '```@all```',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '```\n@all\n```',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '```````\n@all\n```````',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '```code\n@all\n```',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '~~~@all~~~',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '~~~\n@all\n~~~',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: ' /not_cmd @all',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@channel',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@channel.',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@channel/test',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: 'test/@channel',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@all/@channel',
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
text: '@cha*nnel*',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@cha**nnel**',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '*@cha*nnel',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '[@chan](https://google.com)nel',
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
text: '@channel',
|
||||
result: false,
|
||||
},
|
||||
]) {
|
||||
const wrapper = shallowWithIntl(
|
||||
<PostDraft {...baseProps}/>,
|
||||
);
|
||||
const containsAtChannel = wrapper.instance().textContainsAtAllAtChannel(data.text);
|
||||
assert.equal(containsAtChannel, data.result, data.text);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
export const AT_MENTION_REGEX = /\B(@([^@\r\n\s]*))$/i;
|
||||
|
||||
export const AT_MENTION_REGEX_GLOBAL = /\B(@([^@\r\n\s]*))/gi;
|
||||
|
||||
export const AT_MENTION_SEARCH_REGEX = /\bfrom:\s*(\S*)$/i;
|
||||
|
||||
export const CHANNEL_MENTION_REGEX = /\B(~([^~\r\n]*))$/i;
|
||||
|
|
@ -11,4 +13,6 @@ export const CHANNEL_MENTION_SEARCH_REGEX = /\b(?:in|channel):\s*(\S*)$/i;
|
|||
|
||||
export const DATE_MENTION_SEARCH_REGEX = /\b(?:on|before|after):\s*(\S*)$/i;
|
||||
|
||||
export const ALL_SEARCH_FLAGS_REGEX = /\b\w+:/g;
|
||||
export const ALL_SEARCH_FLAGS_REGEX = /\b\w+:/g;
|
||||
|
||||
export const CODE_REGEX = /(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)| *(`{3,}|~{3,})[ .]*(\S+)? *\n([\s\S]*?\s*)\3 *(?:\n+|$)/g;
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ export default keyMirror({
|
|||
|
||||
RECEIVED_CHANNEL_MODERATIONS: null,
|
||||
|
||||
RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP: null,
|
||||
|
||||
RECEIVED_TOTAL_CHANNEL_COUNT: null,
|
||||
|
||||
POST_UNREAD_SUCCESS: null,
|
||||
|
|
|
|||
|
|
@ -2320,4 +2320,34 @@ describe('Actions.Channels', () => {
|
|||
assert.equal(moderations[0].roles.members, true);
|
||||
assert.equal(moderations[0].roles.guests, false);
|
||||
});
|
||||
|
||||
it('getChannelMemberCountsByGroup', async () => {
|
||||
const channelID = 'cid10000000000000000000000';
|
||||
|
||||
nock(Client4.getBaseRoute()).get(
|
||||
`/channels/${channelID}/member_counts_by_group?include_timezones=true`).
|
||||
reply(200, [
|
||||
{
|
||||
group_id: 'group-1',
|
||||
channel_member_count: 1,
|
||||
channel_member_timezones_count: 1,
|
||||
},
|
||||
{
|
||||
group_id: 'group-2',
|
||||
channel_member_count: 999,
|
||||
channel_member_timezones_count: 131,
|
||||
},
|
||||
]);
|
||||
|
||||
await store.dispatch(Actions.getChannelMemberCountsByGroup(channelID, true));
|
||||
|
||||
const channelMemberCounts = store.getState().entities.channels.channelMemberCountsByGroup[channelID];
|
||||
assert.equal(channelMemberCounts['group-1'].group_id, 'group-1');
|
||||
assert.equal(channelMemberCounts['group-1'].channel_member_count, 1);
|
||||
assert.equal(channelMemberCounts['group-1'].channel_member_timezones_count, 1);
|
||||
|
||||
assert.equal(channelMemberCounts['group-2'].group_id, 'group-2');
|
||||
assert.equal(channelMemberCounts['group-2'].channel_member_count, 999);
|
||||
assert.equal(channelMemberCounts['group-2'].channel_member_timezones_count, 131);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1475,6 +1475,26 @@ export function patchChannelModerations(channelId: string, patch: Array<ChannelM
|
|||
});
|
||||
}
|
||||
|
||||
export function getChannelMemberCountsByGroup(channelId: string, includeTimezones: boolean): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
let channelMemberCountsByGroup;
|
||||
try {
|
||||
channelMemberCountsByGroup = await Client4.getChannelMemberCountsByGroup(channelId, includeTimezones);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
if (channelMemberCountsByGroup.length) {
|
||||
dispatch({
|
||||
type: ChannelTypes.RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP,
|
||||
data: {channelId, memberCounts: channelMemberCountsByGroup},
|
||||
});
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
selectChannel,
|
||||
createChannel,
|
||||
|
|
|
|||
|
|
@ -1554,6 +1554,13 @@ export default class Client4 {
|
|||
);
|
||||
};
|
||||
|
||||
getChannelMemberCountsByGroup = async (channelId: string, includeTimezones: boolean) => {
|
||||
return this.doFetch(
|
||||
`${this.getChannelRoute(channelId)}/member_counts_by_group?include_timezones=${includeTimezones}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
|
||||
viewMyChannel = async (channelId: string, prevChannelId?: string) => {
|
||||
const data = {channel_id: channelId, prev_channel_id: prevChannelId};
|
||||
return this.doFetch(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -66,6 +67,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -101,6 +103,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -137,6 +140,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -171,6 +175,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -209,6 +214,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -244,6 +250,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -282,6 +289,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -316,6 +324,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -349,6 +358,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -381,6 +391,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -459,6 +470,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -504,6 +516,7 @@ describe('channels', () => {
|
|||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -550,6 +563,7 @@ describe('channels', () => {
|
|||
},
|
||||
}],
|
||||
},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
|
|
@ -576,4 +590,121 @@ describe('channels', () => {
|
|||
expect(nextState.channelModerations.channel1[0].roles.guests).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP', () => {
|
||||
test('Should add new channel member counts', () => {
|
||||
const state = deepFreeze({
|
||||
channelsInTeam: {},
|
||||
currentChannelId: '',
|
||||
groupsAssociatedToChannel: {},
|
||||
myMembers: {},
|
||||
stats: {},
|
||||
totalCount: 0,
|
||||
membersInChannel: {},
|
||||
channels: {
|
||||
channel1: {
|
||||
id: 'channel1',
|
||||
team_id: 'team',
|
||||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
type: ChannelTypes.RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP,
|
||||
sync: true,
|
||||
currentChannelId: 'channel1',
|
||||
teamId: 'team',
|
||||
data: {
|
||||
channelId: 'channel1',
|
||||
memberCounts: [
|
||||
{
|
||||
group_id: 'group-1',
|
||||
channel_member_count: 1,
|
||||
channel_member_timezones_count: 1,
|
||||
},
|
||||
{
|
||||
group_id: 'group-2',
|
||||
channel_member_count: 999,
|
||||
channel_member_timezones_count: 131,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-1'].channel_member_count).toEqual(1);
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-1'].channel_member_timezones_count).toEqual(1);
|
||||
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-2'].channel_member_count).toEqual(999);
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-2'].channel_member_timezones_count).toEqual(131);
|
||||
});
|
||||
|
||||
test('Should replace existing channel member counts', () => {
|
||||
const state = deepFreeze({
|
||||
channelsInTeam: {},
|
||||
currentChannelId: '',
|
||||
groupsAssociatedToChannel: {},
|
||||
myMembers: {},
|
||||
stats: {},
|
||||
totalCount: 0,
|
||||
membersInChannel: {},
|
||||
channels: {
|
||||
channel1: {
|
||||
id: 'channel1',
|
||||
team_id: 'team',
|
||||
},
|
||||
},
|
||||
channelModerations: {},
|
||||
channelMemberCountsByGroup: {
|
||||
'group-1': {
|
||||
group_id: 'group-1',
|
||||
channel_member_count: 1,
|
||||
channel_member_timezones_count: 1,
|
||||
},
|
||||
'group-2': {
|
||||
group_id: 'group-2',
|
||||
channel_member_count: 999,
|
||||
channel_member_timezones_count: 131,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const nextState = channelsReducer(state, {
|
||||
type: ChannelTypes.RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP,
|
||||
sync: true,
|
||||
currentChannelId: 'channel1',
|
||||
teamId: 'team',
|
||||
data: {
|
||||
channelId: 'channel1',
|
||||
memberCounts: [
|
||||
{
|
||||
group_id: 'group-1',
|
||||
channel_member_count: 5,
|
||||
channel_member_timezones_count: 2,
|
||||
},
|
||||
{
|
||||
group_id: 'group-2',
|
||||
channel_member_count: 1002,
|
||||
channel_member_timezones_count: 133,
|
||||
},
|
||||
{
|
||||
group_id: 'group-3',
|
||||
channel_member_count: 12,
|
||||
channel_member_timezones_count: 13,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-1'].channel_member_count).toEqual(5);
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-1'].channel_member_timezones_count).toEqual(2);
|
||||
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-2'].channel_member_count).toEqual(1002);
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-2'].channel_member_timezones_count).toEqual(133);
|
||||
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-3'].channel_member_count).toEqual(12);
|
||||
expect(nextState.channelMemberCountsByGroup.channel1['group-3'].channel_member_timezones_count).toEqual(13);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {combineReducers} from 'redux';
|
|||
import {ChannelTypes, UserTypes, SchemeTypes, GroupTypes} from '@mm-redux/action_types';
|
||||
import {General} from '../../constants';
|
||||
import {GenericAction} from '@mm-redux/types/actions';
|
||||
import {Channel, ChannelMembership, ChannelStats} from '@mm-redux/types/channels';
|
||||
import {Channel, ChannelMembership, ChannelStats, ChannelMemberCountByGroup, ChannelMemberCountsByGroup} from '@mm-redux/types/channels';
|
||||
import {RelationOneToMany, RelationOneToOne, IDMappedObjects, UserIDMappedObjects} from '@mm-redux/types/utilities';
|
||||
import {Team} from '@mm-redux/types/teams';
|
||||
|
||||
|
|
@ -664,6 +664,33 @@ export function channelModerations(state: any = {}, action: GenericAction) {
|
|||
}
|
||||
}
|
||||
|
||||
export function channelMemberCountsByGroup(state: any = {}, action: GenericAction) {
|
||||
switch (action.type) {
|
||||
case ChannelTypes.RECEIVED_CHANNEL_MEMBER_COUNTS_BY_GROUP: {
|
||||
const {channelId, memberCounts} = action.data;
|
||||
const memberCountsByGroup: ChannelMemberCountsByGroup = {};
|
||||
memberCounts.forEach((channelMemberCount: ChannelMemberCountByGroup) => {
|
||||
if (!state[channelId]?.[channelMemberCount.group_id] ||
|
||||
state[channelId]?.[channelMemberCount.group_id]?.channel_member_count !== channelMemberCount.channel_member_count ||
|
||||
state[channelId]?.[channelMemberCount.group_id]?.channel_member_timezones_count !== channelMemberCount.channel_member_timezones_count) {
|
||||
memberCountsByGroup[channelMemberCount.group_id] = channelMemberCount;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(memberCountsByGroup).length > 0) {
|
||||
return {
|
||||
...state,
|
||||
[channelId]: memberCountsByGroup,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
|
||||
// the current selected channel
|
||||
|
|
@ -693,4 +720,7 @@ export default combineReducers({
|
|||
|
||||
// object where every key is the channel id and has an object with the channel moderations
|
||||
channelModerations,
|
||||
|
||||
// object where every key is the channel id containing one or several object(s) with a mapping of <group_id: ChannelMemberCountByGroup>
|
||||
channelMemberCountsByGroup,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3811,3 +3811,32 @@ test('Selectors.Channels.getChannelModerations', () => {
|
|||
assert.equal(Selectors.getChannelModerations(state, undefined), undefined);
|
||||
assert.equal(Selectors.getChannelModerations(state, 'undefined'), undefined);
|
||||
});
|
||||
|
||||
test('Selectors.Channels.getChannelMemberCountsByGroup', () => {
|
||||
const memberCounts = {
|
||||
'group-1': {
|
||||
group_id: 'group-1',
|
||||
channel_member_count: 1,
|
||||
channel_member_timezones_count: 1,
|
||||
},
|
||||
'group-2': {
|
||||
group_id: 'group-2',
|
||||
channel_member_count: 999,
|
||||
channel_member_timezones_count: 131,
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
entities: {
|
||||
channels: {
|
||||
channelMemberCountsByGroup: {
|
||||
channel1: memberCounts,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
assert.deepEqual(Selectors.getChannelMemberCountsByGroup(state, 'channel1'), memberCounts);
|
||||
assert.deepEqual(Selectors.getChannelMemberCountsByGroup(state, undefined), {});
|
||||
assert.deepEqual(Selectors.getChannelMemberCountsByGroup(state, 'undefined'), {});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {createIdsSelector} from '@mm-redux/utils/helpers';
|
|||
|
||||
export {getCurrentChannelId, getMyChannelMemberships, getMyCurrentChannelMembership};
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Channel, ChannelStats, ChannelMembership, ChannelModeration} from '@mm-redux/types/channels';
|
||||
import {Channel, ChannelStats, ChannelMembership, ChannelModeration, ChannelMemberCountsByGroup} from '@mm-redux/types/channels';
|
||||
import {UsersState, UserProfile} from '@mm-redux/types/users';
|
||||
import {PreferenceType} from '@mm-redux/types/preferences';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
|
|
@ -930,3 +930,7 @@ export function isManuallyUnread(state: GlobalState, channelId?: string): boolea
|
|||
export function getChannelModerations(state: GlobalState, channelId: string): Array<ChannelModeration> {
|
||||
return state.entities.channels.channelModerations[channelId];
|
||||
}
|
||||
|
||||
export function getChannelMemberCountsByGroup(state: GlobalState, channelId: string): ChannelMemberCountsByGroup {
|
||||
return state.entities.channels.channelMemberCountsByGroup[channelId] || {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,13 @@ export function getAssociatedGroupsForReference(state: GlobalState, teamId: stri
|
|||
return groupsForReference.sort((groupA: Group, groupB: Group) => groupA.name.localeCompare(groupB.name, locale));
|
||||
}
|
||||
|
||||
export const getAssociatedGroupsForReferenceMap = reselect.createSelector(
|
||||
getAssociatedGroupsForReference,
|
||||
(allGroups) => {
|
||||
return new Map(allGroups.map((group) => [`@${group.name}`, group]));
|
||||
},
|
||||
);
|
||||
|
||||
const teamGroupIDs = (state: GlobalState, teamID: string) => state.entities.teams.groupsAssociatedToTeam[teamID]?.ids || [];
|
||||
|
||||
const channelGroupIDs = (state: GlobalState, channelID: string) => state.entities.channels.groupsAssociatedToChannel[channelID]?.ids || [];
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export type ChannelsState = {
|
|||
totalCount: number;
|
||||
manuallyUnread: RelationOneToOne<Channel, boolean>;
|
||||
channelModerations: RelationOneToOne<Channel, Array<ChannelModeration>>;
|
||||
channelMemberCountsByGroup: RelationOneToOne<Channel, ChannelMemberCountsByGroup>;
|
||||
};
|
||||
|
||||
export type ChannelModeration = {
|
||||
|
|
@ -98,3 +99,11 @@ export type ChannelModerationPatch = {
|
|||
members?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type ChannelMemberCountByGroup = {
|
||||
group_id: string;
|
||||
channel_member_count: number;
|
||||
channel_member_timezones_count: number;
|
||||
};
|
||||
|
||||
export type ChannelMemberCountsByGroup = Record<string, ChannelMemberCountByGroup>;
|
||||
|
|
|
|||
|
|
@ -379,6 +379,11 @@
|
|||
"mobile.post_textbox.entire_channel.message": "By using @all or @channel you are about to send notifications to {totalMembers, number} {totalMembers, plural, one {person} other {people}}. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.entire_channel.message.with_timezones": "By using @all or @channel you are about to send notifications to {totalMembers, number} {totalMembers, plural, one {person} other {people}} in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.entire_channel.title": "Confirm sending notifications to entire channel",
|
||||
"mobile.post_textbox.groups.title": "Confirm sending notifications to groups",
|
||||
"mobile.post_textbox.multi_group.message.with_timezones": "By using {mentions} and {finalMention} you are about to send notifications to at least {totalMembers} people in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.multi_group.message.without_timezones": "By using {mentions} and {finalMention} you are about to send notifications to at least {totalMembers} people. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.one_group.message.with_timezones": "By using {mention} you are about to send notifications to {totalMembers} people in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.one_group.message.without_timezones": "By using {mention} you are about to send notifications to {totalMembers} people. Are you sure you want to do this?",
|
||||
"mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server. Are you sure you want to post the message?",
|
||||
"mobile.post_textbox.uploadFailedTitle": "Attachment failure",
|
||||
"mobile.post.cancel": "Cancel",
|
||||
|
|
|
|||
Loading…
Reference in a new issue