diff --git a/webapp/channels/src/components/profile_popover/profile_popover.test.tsx b/webapp/channels/src/components/profile_popover/profile_popover.test.tsx index ffa50baaa7..eb409974d0 100644 --- a/webapp/channels/src/components/profile_popover/profile_popover.test.tsx +++ b/webapp/channels/src/components/profile_popover/profile_popover.test.tsx @@ -148,6 +148,9 @@ function getBasePropsAndState(): [Props, DeepPartial] { // @ts-ignore 'plugins-com.mattermost.calls': { sessions: {}, + callsConfig: { + DefaultEnabled: true, + }, }, }; const props: Props = { @@ -301,7 +304,7 @@ describe('components/ProfilePopover', () => { expect(await screen.findByLabelText('Start Call')).toBeInTheDocument(); }); - test('should not show start a call button when calls are disabled', async () => { + test('should not show start call button when plugin is disabled', async () => { const [props, initialState] = getBasePropsAndState(); initialState.plugins!.plugins = {}; @@ -318,11 +321,47 @@ describe('components/ProfilePopover', () => { expect(button).toBeDisabled(); }); - test('should not show the start call button when callsChannelState.enabled is false', async () => { - (Client4.getCallsChannelState as jest.Mock).mockImplementationOnce(async () => ({enabled: false})); + test('should not show start call button when calls in channel have been explicitly disabled', async () => { const [props, initialState] = getBasePropsAndState(); + (initialState as any)['plugins-com.mattermost.calls'].channels = {dmChannelId: {enabled: false}}; renderWithPluginReducers(, initialState); - expect(await screen.findByLabelText('Start Call')).not.toBeInTheDocument(); + expect(await screen.queryByLabelText('Start Call')).not.toBeInTheDocument(); + expect(await screen.queryByLabelText('Call with user is ongoing')).not.toBeInTheDocument(); + }); + + test('should not show start call button for users when calls test mode is on', async () => { + const [props, initialState] = getBasePropsAndState(); + (initialState as any)['plugins-com.mattermost.calls'].callsConfig = {DefaultEnabled: false}; + + renderWithPluginReducers(, initialState); + expect(await screen.queryByLabelText('Start Call')).not.toBeInTheDocument(); + }); + + test('should show start call button for users when calls test mode is on if calls in channel have been explicitly enabled', async () => { + const [props, initialState] = getBasePropsAndState(); + (initialState as any)['plugins-com.mattermost.calls'].callsConfig = {DefaultEnabled: false}; + (initialState as any)['plugins-com.mattermost.calls'].channels = {dmChannelId: {enabled: true}}; + + renderWithPluginReducers(, initialState); + expect(await screen.queryByLabelText('Start Call')).toBeInTheDocument(); + }); + + test('should show start call button for admin when calls test mode is on', async () => { + const [props, initialState] = getBasePropsAndState(); + (initialState as any)['plugins-com.mattermost.calls'].callsConfig = {DefaultEnabled: false}; + initialState.entities = { + ...initialState.entities!, + users: { + ...initialState.entities!.users, + profiles: { + ...initialState.entities!.users!.profiles, + currentUser: TestHelper.getUserMock({id: 'currentUser', roles: General.SYSTEM_ADMIN_ROLE}), + }, + }, + }; + + renderWithPluginReducers(, initialState); + expect(await screen.findByLabelText('Start Call')).toBeInTheDocument(); }); }); diff --git a/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.tsx b/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.tsx index 42298a8cab..a8b79f768f 100644 --- a/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.tsx +++ b/webapp/channels/src/components/profile_popover/profile_popover_call_button_wrapper/index.tsx @@ -1,14 +1,21 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback, useEffect, useState} from 'react'; +import React from 'react'; import {useIntl} from 'react-intl'; import {useSelector} from 'react-redux'; -import {Client4} from 'mattermost-redux/client'; import {getChannelByName} from 'mattermost-redux/selectors/entities/channels'; +import {getUser} from 'mattermost-redux/selectors/entities/users'; +import {isSystemAdmin} from 'mattermost-redux/utils/user_utils'; -import {isCallsEnabled as getIsCallsEnabled, getSessionsInCalls} from 'selectors/calls'; +import { + isCallsEnabled as getIsCallsEnabled, + getSessionsInCalls, + getCallsConfig, + callsChannelExplicitlyDisabled, + callsChannelExplicitlyEnabled, +} from 'selectors/calls'; import ProfilePopoverCallButton from 'components/profile_popover/profile_popover_calls_button'; import WithTooltip from 'components/with_tooltip'; @@ -24,11 +31,6 @@ type Props = { username: string; } -type ChannelCallsState = { - enabled: boolean; - id: string; -}; - export function isUserInCall(state: GlobalState, userId: string, channelId: string) { const sessionsInCall = getSessionsInCalls(state)[channelId] || {}; @@ -52,6 +54,36 @@ const CallButton = ({ const isCallsEnabled = useSelector((state: GlobalState) => getIsCallsEnabled(state)); const dmChannel = useSelector((state: GlobalState) => getChannelByName(state, getDirectChannelName(currentUserId, userId))); + const shouldRenderButton = useSelector((state: GlobalState) => { + // 1. No one should get the button if the plugin is disabled. + if (!isCallsEnabled) { + return false; + } + + // 2. No one should get the button if calls in channel have been explicitly disabled in the DM channel. + if (callsChannelExplicitlyDisabled(state, dmChannel?.id ?? '')) { + return false; + } + + // 3. Admins should get the button unless calls have been explicitly disabled in the DM channel. This + // should apply in test mode as well (DefaultEnabled = false). + if (isSystemAdmin(getUser(state, currentUserId)?.roles)) { + return true; + } + + // 4. Users should only see the button if test mode is off (DefaultEnabled = true) and calls in the DM channel are not disabled. + if (getCallsConfig(state).DefaultEnabled) { + return true; + } + + // 5. Everyone should see the button if calls have been explicitly enabled in the DM channel, regardless of test mode state. + if (callsChannelExplicitlyEnabled(state, dmChannel?.id ?? '')) { + return true; + } + + return false; + }); + const hasDMCall = useSelector((state: GlobalState) => { if (isCallsEnabled && dmChannel) { return isUserInCall(state, currentUserId, dmChannel.id) || isUserInCall(state, userId, dmChannel.id); @@ -59,28 +91,7 @@ const CallButton = ({ return false; }); - const [callsDMChannelState, setCallsDMChannelState] = useState(); - - const getCallsChannelState = useCallback((channelId: string): Promise => { - let data: Promise; - try { - data = Client4.getCallsChannelState(channelId); - } catch (error) { - return error; - } - - return data; - }, []); - - useEffect(() => { - if (isCallsEnabled && dmChannel) { - getCallsChannelState(dmChannel.id).then((data) => { - setCallsDMChannelState(data); - }); - } - }, []); - - if (!isCallsEnabled || callsDMChannelState?.enabled === false) { + if (!shouldRenderButton) { return null; } diff --git a/webapp/channels/src/selectors/calls.ts b/webapp/channels/src/selectors/calls.ts index 592bcd7c43..0d6d750d28 100644 --- a/webapp/channels/src/selectors/calls.ts +++ b/webapp/channels/src/selectors/calls.ts @@ -9,7 +9,7 @@ import {suitePluginIds} from 'utils/constants'; import type {GlobalState} from 'types/store'; -const CALLS_PLUGIN = 'plugins-com.mattermost.calls'; +const CALLS_PLUGIN = `plugins-${suitePluginIds.calls}`; export function isCallsEnabled(state: GlobalState, minVersion = '0.4.2') { return Boolean(state.plugins.plugins[suitePluginIds.calls] && @@ -20,17 +20,37 @@ export function isCallsEnabled(state: GlobalState, minVersion = '0.4.2') { export function isCallsRingingEnabledOnServer(state: GlobalState) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return Boolean(state[`plugins-${suitePluginIds.calls}`]?.callsConfig?.EnableRinging); + return Boolean(state[CALLS_PLUGIN]?.callsConfig?.EnableRinging); } export function getSessionsInCalls(state: GlobalState): Record> { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return state[CALLS_PLUGIN].sessions || {}; + return state[CALLS_PLUGIN]?.sessions || {}; } export function getCallsConfig(state: GlobalState): CallsConfig { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return state[CALLS_PLUGIN].callsConfig; + return state[CALLS_PLUGIN]?.callsConfig; +} + +export function getCallsChannelState(state: GlobalState, channelId: string): {enabled?: boolean} { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (!state[CALLS_PLUGIN] || !state[CALLS_PLUGIN].channels) { + return {}; + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + return state[CALLS_PLUGIN].channels[channelId] || {}; +} + +export function callsChannelExplicitlyEnabled(state: GlobalState, channelId: string) { + return Boolean(getCallsChannelState(state, channelId).enabled); +} + +export function callsChannelExplicitlyDisabled(state: GlobalState, channelId: string) { + const enabled = getCallsChannelState(state, channelId).enabled; + return (typeof enabled !== 'undefined') && !enabled; }