forked from Ivasoft/mattermost-mobile
* Implement MaxCallParticipants config setting
* Add test
(cherry picked from commit bb655c8c60)
Co-authored-by: Claudio Costa <cstcld91@gmail.com>
This commit is contained in:
@@ -32,7 +32,7 @@ type CallMessageProps = {
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
intl: typeof IntlShape;
|
||||
isCloudLimitRestricted: boolean;
|
||||
isLimitRestricted: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -121,11 +121,11 @@ const CallMessage = ({
|
||||
currentChannelName,
|
||||
callChannelName,
|
||||
intl,
|
||||
isCloudLimitRestricted,
|
||||
isLimitRestricted,
|
||||
}: CallMessageProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const joinHandler = () => {
|
||||
if (alreadyInTheCall || isCloudLimitRestricted) {
|
||||
if (alreadyInTheCall || isLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(intl, post.channel_id, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
|
||||
@@ -187,15 +187,15 @@ const CallMessage = ({
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[style.joinCallButton, isCloudLimitRestricted && style.joinCallButtonRestricted]}
|
||||
style={[style.joinCallButton, isLimitRestricted && style.joinCallButtonRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-outline'
|
||||
size={16}
|
||||
style={[style.joinCallButtonIcon, isCloudLimitRestricted && style.joinCallButtonIconRestricted]}
|
||||
style={[style.joinCallButtonIcon, isLimitRestricted && style.joinCallButtonIconRestricted]}
|
||||
/>
|
||||
<Text style={[style.joinCallButtonText, isCloudLimitRestricted && style.joinCallButtonTextRestricted]}>
|
||||
<Text style={[style.joinCallButtonText, isLimitRestricted && style.joinCallButtonTextRestricted]}>
|
||||
{joinCallButtonText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
||||
import {getUser, getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import CallMessage from './call_message';
|
||||
|
||||
@@ -41,7 +41,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
userTimezone: enableTimezone ? getUserCurrentTimezone(currentUser.timezone) : undefined,
|
||||
currentChannelName: getChannel(state, post.channel_id)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isCloudLimitRestricted: isCloudLimitRestricted(state, post.channel_id),
|
||||
isLimitRestricted: isLimitRestricted(state, post.channel_id),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
|
||||
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
@@ -28,7 +28,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
||||
const currentCall = useSelector(getCurrentCall);
|
||||
const currentCallChannelId = currentCall?.channelId || '';
|
||||
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
|
||||
const cloudLimitRestricted = useSelector(isCloudLimitRestricted);
|
||||
const limitRestricted = useSelector(isLimitRestricted);
|
||||
|
||||
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
|
||||
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
|
||||
@@ -40,7 +40,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
||||
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
|
||||
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
|
||||
|
||||
if (alreadyInTheCall || cloudLimitRestricted) {
|
||||
if (alreadyInTheCall || limitRestricted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {bindActionCreators, Dispatch} from 'redux';
|
||||
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import JoinCall from './join_call';
|
||||
|
||||
@@ -23,7 +23,7 @@ function mapStateToProps(state: GlobalState) {
|
||||
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
|
||||
currentChannelName: getChannel(state, currentChannelId)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isCloudLimitRestricted: isCloudLimitRestricted(state),
|
||||
isLimitRestricted: isLimitRestricted(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('JoinCall', () => {
|
||||
alreadyInTheCall: false,
|
||||
currentChannelName: 'Current Channel',
|
||||
callChannelName: 'Call Channel',
|
||||
isCloudLimitRestricted: false,
|
||||
isLimitRestricted: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
||||
@@ -26,7 +26,7 @@ type Props = {
|
||||
alreadyInTheCall: boolean;
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
isCloudLimitRestricted: boolean;
|
||||
isLimitRestricted: boolean;
|
||||
intl: typeof IntlShape;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ const JoinCall = (props: Props) => {
|
||||
}, [props.call, props.alreadyInTheCall]);
|
||||
|
||||
const joinHandler = () => {
|
||||
if (props.isCloudLimitRestricted) {
|
||||
if (props.isLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(props.intl, props.call.channelId, props.callChannelName, props.currentChannelName, props.confirmToJoin, props.actions.joinCall);
|
||||
@@ -114,7 +114,7 @@ const JoinCall = (props: Props) => {
|
||||
return (
|
||||
<View style={style.outerContainer}>
|
||||
<Pressable
|
||||
style={[style.innerContainer, props.isCloudLimitRestricted && style.innerContainerRestricted]}
|
||||
style={[style.innerContainer, props.isLimitRestricted && style.innerContainerRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
@@ -123,7 +123,7 @@ const JoinCall = (props: Props) => {
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
<Text style={style.joinCall}>{'Join Call'}</Text>
|
||||
{props.isCloudLimitRestricted ?
|
||||
{props.isLimitRestricted ?
|
||||
<Text style={style.limitReached}>
|
||||
{'Participant limit reached'}
|
||||
</Text> :
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
import assert from 'assert';
|
||||
|
||||
import deepFreezeAndThrowOnMutation from '@mm-redux/utils/deep_freeze';
|
||||
import {DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
import * as Selectors from './calls';
|
||||
|
||||
describe('Selectors.Calls', () => {
|
||||
const call1 = {id: 'call1'};
|
||||
const call1 = {id: 'call1', participants: [{id: 'me'}]};
|
||||
const call2 = {id: 'call2'};
|
||||
const testState = deepFreezeAndThrowOnMutation({
|
||||
entities: {
|
||||
@@ -20,6 +21,10 @@ describe('Selectors.Calls', () => {
|
||||
joined: 'call1',
|
||||
enabled: {'channel-1': true, 'channel-2': false},
|
||||
screenShareURL: 'screenshare-url',
|
||||
config: DefaultServerConfig,
|
||||
},
|
||||
general: {
|
||||
license: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -77,4 +82,119 @@ describe('Selectors.Calls', () => {
|
||||
it('getScreenShareURL', () => {
|
||||
assert.equal(Selectors.getScreenShareURL(testState), 'screenshare-url');
|
||||
});
|
||||
|
||||
it('isLimitRestricted', () => {
|
||||
// Default, no limit
|
||||
assert.equal(Selectors.isLimitRestricted(testState, 'call1'), false);
|
||||
|
||||
let newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
MaxCallParticipants: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Limit to 1 and one participant already in call.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
|
||||
// Limit to 1 but no call ongoing.
|
||||
assert.equal(Selectors.isLimitRestricted(newState), false);
|
||||
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// On cloud, no limit.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), false);
|
||||
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
config: {
|
||||
...testState.entities.calls.config,
|
||||
MaxCallParticipants: 1,
|
||||
},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// On cloud, with limit.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
|
||||
const call = {id: 'call1',
|
||||
participants: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]};
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
calls: {call1: call},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete newState.entities.calls.config.MaxCallParticipants;
|
||||
|
||||
// On cloud, MaxCallParticipants missing, default should be used.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), false);
|
||||
|
||||
const newCall = {id: 'call1',
|
||||
participants: [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]};
|
||||
newState = {
|
||||
...testState,
|
||||
entities: {
|
||||
...testState.entities,
|
||||
calls: {
|
||||
...testState.entities.calls,
|
||||
calls: {call1: newCall},
|
||||
},
|
||||
general: {
|
||||
license: {Cloud: 'true'},
|
||||
},
|
||||
},
|
||||
};
|
||||
delete newState.entities.calls.config.MaxCallParticipants;
|
||||
|
||||
// On cloud, MaxCallParticipants missing, default should be used.
|
||||
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,37 +92,25 @@ const isCloud: (state: GlobalState) => boolean = createSelector(
|
||||
(license) => license?.Cloud === 'true',
|
||||
);
|
||||
|
||||
// NOTE: Calls v0.5.3 will not return sku_short_name in config, so this will fail
|
||||
const isCloudProfessionalOrEnterprise: (state: GlobalState) => boolean = createSelector(
|
||||
export const isLimitRestricted: (state: GlobalState, channelId?: string) => boolean = createSelector(
|
||||
isCloud,
|
||||
getLicense,
|
||||
getConfig,
|
||||
(cloud, license, config) => {
|
||||
return cloud && (config.sku_short_name === 'professional' || config.sku_short_name === 'enterprise');
|
||||
},
|
||||
);
|
||||
|
||||
export const isCloudLimitRestricted: (state: GlobalState, channelId?: string) => boolean = createSelector(
|
||||
isCloud,
|
||||
isCloudProfessionalOrEnterprise,
|
||||
(state: GlobalState, channelId: string) => (channelId ? getCalls(state)[channelId] : getCallInCurrentChannel(state)),
|
||||
getConfig,
|
||||
(cloud, isCloudPaid, call, config) => {
|
||||
if (!call || !cloud) {
|
||||
(cloud, call, config) => {
|
||||
if (!call) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const numParticipants = Object.keys(call.participants || {}).length;
|
||||
|
||||
// TODO: The next block is used for case when cloud server is using Calls v0.5.3. This can be removed
|
||||
// when v0.5.4 is prepackaged in cloud. Then replace the max in the return statement with
|
||||
// config.cloud_max_participants, and replace cloudPaid with isCloudPaid
|
||||
let max = config.cloud_max_participants;
|
||||
let cloudPaid = isCloudPaid;
|
||||
// config.MaxCallParticipants.
|
||||
let max = config.MaxCallParticipants;
|
||||
if (cloud && !max) {
|
||||
// We're not sure if we're in cloud paid because this could be v0.5.3, so assume we are for now (the server will prevent calls)
|
||||
cloudPaid = true;
|
||||
max = Calls.DefaultCloudMaxParticipants;
|
||||
}
|
||||
|
||||
return cloudPaid && Object.keys(call.participants || {}).length >= max;
|
||||
return max !== 0 && numParticipants >= max;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -62,8 +62,8 @@ export type ServerConfig = {
|
||||
ICEServers: string[];
|
||||
AllowEnableCalls: boolean;
|
||||
DefaultEnabled: boolean;
|
||||
MaxCallParticipants: number;
|
||||
sku_short_name: string;
|
||||
cloud_max_participants: number;
|
||||
last_retrieved_at: number;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const DefaultServerConfig = {
|
||||
ICEServers: [],
|
||||
AllowEnableCalls: false,
|
||||
DefaultEnabled: false,
|
||||
MaxCallParticipants: 0,
|
||||
sku_short_name: '',
|
||||
cloud_max_participants: 0,
|
||||
last_retrieved_at: 0,
|
||||
} as ServerConfig;
|
||||
|
||||
Reference in New Issue
Block a user