Skip to content

Commit

Permalink
[MM-44651] Implement MaxCallParticipants config setting (mattermost#6334
Browse files Browse the repository at this point in the history
)

* Implement MaxCallParticipants config setting

* Add test
  • Loading branch information
streamer45 authored Jun 3, 2022
1 parent 23509cb commit bb655c8
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 41 deletions.
12 changes: 6 additions & 6 deletions app/products/calls/components/call_message/call_message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type CallMessageProps = {
currentChannelName: string;
callChannelName: string;
intl: typeof IntlShape;
isCloudLimitRestricted: boolean;
isLimitRestricted: boolean;
}

const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions app/products/calls/components/call_message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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),
};
}

Expand Down
6 changes: 3 additions & 3 deletions app/products/calls/components/channel_info/start_call.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions app/products/calls/components/join_call/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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),
};
}

Expand Down
2 changes: 1 addition & 1 deletion app/products/calls/components/join_call/join_call.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('JoinCall', () => {
alreadyInTheCall: false,
currentChannelName: 'Current Channel',
callChannelName: 'Call Channel',
isCloudLimitRestricted: false,
isLimitRestricted: false,
};

test('should match snapshot', () => {
Expand Down
8 changes: 4 additions & 4 deletions app/products/calls/components/join_call/join_call.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Props = {
alreadyInTheCall: boolean;
currentChannelName: string;
callChannelName: string;
isCloudLimitRestricted: boolean;
isLimitRestricted: boolean;
intl: typeof IntlShape;
}

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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> :
Expand Down
122 changes: 121 additions & 1 deletion app/products/calls/store/selectors/calls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -20,6 +21,10 @@ describe('Selectors.Calls', () => {
joined: 'call1',
enabled: {'channel-1': true, 'channel-2': false},
screenShareURL: 'screenshare-url',
config: DefaultServerConfig,
},
general: {
license: {},
},
},
});
Expand Down Expand Up @@ -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);
});
});
28 changes: 8 additions & 20 deletions app/products/calls/store/selectors/calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
);
4 changes: 2 additions & 2 deletions app/products/calls/store/types/calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,16 @@ export type ServerConfig = {
ICEServers: string[];
AllowEnableCalls: boolean;
DefaultEnabled: boolean;
MaxCallParticipants: number;
sku_short_name: string;
cloud_max_participants: number;
last_retrieved_at: number;
}

export const DefaultServerConfig = {
ICEServers: [],
AllowEnableCalls: false,
DefaultEnabled: false,
MaxCallParticipants: 0,
sku_short_name: '',
cloud_max_participants: 0,
last_retrieved_at: 0,
} as ServerConfig;

0 comments on commit bb655c8

Please sign in to comment.