Skip to content

Commit

Permalink
[IMPROVE] Added identification on calls to/from existing contacts (#2…
Browse files Browse the repository at this point in the history
…6334)

Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com>
Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com>
Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 25, 2022
1 parent 68bc2ae commit 10932a5
Show file tree
Hide file tree
Showing 20 changed files with 153 additions and 99 deletions.
2 changes: 1 addition & 1 deletion apps/meteor/client/components/FilterByText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const FilterByText = ({
}, []);

return (
<Box mb='x16' is='form' onSubmit={handleFormSubmit} display='flex' flexDirection={shouldFiltersStack ? 'column' : 'row'} {...props}>
<Box mb='x16' is='form' onSubmit={handleFormSubmit} display='flex' flexDirection={shouldFiltersStack ? 'column' : 'row'}>
<TextInput
placeholder={placeholder ?? t('Search')}
ref={inputRef}
Expand Down
10 changes: 7 additions & 3 deletions apps/meteor/client/providers/CallProvider/CallProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { OutgoingByeRequest } from 'sip.js/lib/core';
import { CustomSounds } from '../../../app/custom-sounds/client';
import { getUserPreference } from '../../../app/utils/client';
import { isOutboundClient, useVoipClient } from '../../../ee/client/hooks/useVoipClient';
import { parseOutboundPhoneNumber } from '../../../ee/client/lib/voip/parseOutboundPhoneNumber';
import { WrapUpCallModal } from '../../../ee/client/voip/components/modals/WrapUpCallModal';
import { CallContext, CallContextValue, useIsVoipEnterprise } from '../../contexts/CallContext';
import { useDialModal } from '../../hooks/useDialModal';
Expand Down Expand Up @@ -166,11 +167,12 @@ export const CallProvider: FC = ({ children }) => {
return '';
}
try {
const phone = parseOutboundPhoneNumber(caller.callerId);
const { visitor } = await visitorEndpoint({
visitor: {
token: Random.id(),
phone: caller.callerId,
name: caller.callerName || caller.callerId,
phone,
name: caller.callerName || phone,
},
});
const voipRoom = await voipEndpoint({ token: visitor.token, agentId: user._id, direction });
Expand Down Expand Up @@ -389,12 +391,14 @@ export const CallProvider: FC = ({ children }) => {
stopAllRingback();
};

const onCallFailed = (reason: 'Not Found' | 'Address Incomplete' | string): void => {
const onCallFailed = (reason: 'Not Found' | 'Address Incomplete' | 'Request Terminated' | string): void => {
switch (reason) {
case 'Not Found':
// This happens when the call matches dialplan and goes to the world, but the trunk doesnt find the number.
openDialModal({ errorMessage: t('Dialed_number_doesnt_exist') });
break;
case 'Address Incomplete':
// This happens when the dialed number doesnt match a valid asterisk dialplan pattern or the number is invalid.
openDialModal({ errorMessage: t('Dialed_number_is_incomplete') });
break;
case 'Request Terminated':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ const VoipFooterTemplate: ComponentStory<typeof VoipFooter> = (args) => {
callsInQueue='2 Calls In Queue'
dispatchEvent={() => null}
openedRoomInfo={{ v: { token: '' }, rid: '' }}
anonymousText={'Anonymous'}
options={{
deviceSettings: {
label: (
Expand Down
13 changes: 8 additions & 5 deletions apps/meteor/client/sidebar/footer/voip/VoipFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import type { IVoipRoom } from '@rocket.chat/core-typings';
import { ICallerInfo, VoIpCallerInfo, VoipClientEvents } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Box, Button, ButtonGroup, Icon, SidebarFooter, Menu, IconButton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { ReactElement, MouseEvent, ReactNode } from 'react';

import type { VoipFooterMenuOptions } from '../../../../ee/client/hooks/useVoipFooterMenu';
import { parseOutboundPhoneNumber } from '../../../../ee/client/lib/voip/parseOutboundPhoneNumber';
import { CallActionsType } from '../../../contexts/CallContext';
import { useOmnichannelContactLabel } from './hooks/useOmnichannelContactLabel';

type VoipFooterPropsType = {
caller: ICallerInfo;
Expand All @@ -31,7 +32,6 @@ type VoipFooterPropsType = {
openRoom: (rid: IVoipRoom['_id']) => void;
dispatchEvent: (params: { event: VoipClientEvents; rid: string; comment?: string }) => void;
openedRoomInfo: { v: { token?: string | undefined }; rid: string };
anonymousText: string;
isEnterprise: boolean;
children?: ReactNode;
options: VoipFooterMenuOptions;
Expand All @@ -53,11 +53,13 @@ export const VoipFooter = ({
callsInQueue,
dispatchEvent,
openedRoomInfo,
anonymousText,
isEnterprise = false,
children,
options,
}: VoipFooterPropsType): ReactElement => {
const contactLabel = useOmnichannelContactLabel(caller);
const t = useTranslation();

const cssClickable =
callerState === 'IN_CALL' || callerState === 'ON_HOLD'
? css`
Expand Down Expand Up @@ -117,7 +119,7 @@ export const VoipFooter = ({
<Box display='flex' flexDirection='row' mi='16px' mbe='12px' justifyContent='space-between' alignItems='center'>
<Box>
<Box color='white' fontScale='p2' withTruncatedText>
{caller.callerName || parseOutboundPhoneNumber(caller.callerId) || anonymousText}
{contactLabel || t('Anonymous')}
</Box>
<Box color='hint' fontScale='c1' withTruncatedText>
{subtitle}
Expand All @@ -132,6 +134,7 @@ export const VoipFooter = ({
small
square
danger
aria-label={t('End_call')}
onClick={(e): unknown => {
e.stopPropagation();
muted && toggleMic(false);
Expand All @@ -143,7 +146,7 @@ export const VoipFooter = ({
</Button>
)}
{callerState === 'OFFER_RECEIVED' && (
<Button title={tooltips.endCall} small square danger onClick={callActions.reject}>
<Button title={tooltips.endCall} aria-label={t('Reject_call')} small square danger onClick={callActions.reject}>
<Icon name='phone-off' size='x16' />
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ICallerInfo } from '@rocket.chat/core-typings';
import { useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';

import { parseOutboundPhoneNumber } from '../../../../../ee/client/lib/voip/parseOutboundPhoneNumber';

export const useOmnichannelContactLabel = (caller: ICallerInfo): string => {
const getContactBy = useEndpoint('GET', '/v1/omnichannel/contact.search');
const phone = parseOutboundPhoneNumber(caller.callerId);

const { data } = useQuery(['getContactsByPhone', phone], async () => getContactBy({ phone }).then(({ contact }) => contact), {
enabled: !!phone,
});

return data?.name || caller.callerName || phone;
};
1 change: 0 additions & 1 deletion apps/meteor/client/sidebar/footer/voip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export const VoipFooter = (): ReactElement | null => {
callsInQueue={getCallsInQueueText}
dispatchEvent={dispatchEvent}
openedRoomInfo={openedRoomInfo}
anonymousText={t('Anonymous')}
isEnterprise={isEnterprise}
options={options}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const OmniChannelCallDialPad = ({ ...props }): ReactElement => {
icon='dialpad'
onClick={(): void => openDialModal()}
disabled={!outBoundCallsEnabledForUser}
aria-label={t('Open_Dialpad')}
{...props}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import React, { ReactElement } from 'react';

export const OmnichannelCallToggleLoading = ({ ...props }): ReactElement => {
const t = useTranslation();
return <Sidebar.TopBar.Action icon='phone' data-title={t('Loading')} disabled {...props} />;
return <Sidebar.TopBar.Action icon='phone' data-title={t('Loading')} aria-label={t('VoIP_Toggle')} disabled {...props} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,14 @@ export const OmnichannelCallToggleReady = ({ ...props }): ReactElement => {
color: getColor(),
};

return <Sidebar.TopBar.Action disabled={inCall} {...voipCallIcon} {...props} onClick={onClickVoipButton} />;
return (
<Sidebar.TopBar.Action
aria-label={t('VoIP_Toggle')}
aria-checked={registered}
disabled={inCall}
{...voipCallIcon}
{...props}
onClick={onClickVoipButton}
/>
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { css } from '@rocket.chat/css-in-js';
import { IconButton } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import React, { MouseEvent, ReactElement } from 'react';

import { useVoipOutboundStates } from '../../../contexts/CallContext';
import { useDialModal } from '../../../hooks/useDialModal';

export const rcxCallDialButton = css`
.rcx-show-call-button-on-hover:not(:hover) & {
display: none !important;
}
`;

export const CallDialpadButton = ({ phoneNumber }: { phoneNumber: string }): ReactElement => {
const t = useTranslation();

Expand All @@ -20,6 +27,7 @@ export const CallDialpadButton = ({ phoneNumber }: { phoneNumber: string }): Rea
<IconButton
rcx-call-dial-button
title={outBoundCallsAllowed ? t('Call_number') : t('Call_number_enterprise_only')}
className={rcxCallDialButton}
disabled={!outBoundCallsEnabledForUser || !phoneNumber}
tiny
icon='phone'
Expand Down
57 changes: 2 additions & 55 deletions apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import { IVoipRoom } from '@rocket.chat/core-typings';
import { css } from '@rocket.chat/css-in-js';
import { Table } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';
import React, { useState, useMemo, useCallback, FC } from 'react';

import { parseOutboundPhoneNumber } from '../../../../../ee/client/lib/voip/parseOutboundPhoneNumber';
import GenericTable from '../../../../components/GenericTable';
import { useIsCallReady } from '../../../../contexts/CallContext';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import { CallDialpadButton } from '../CallDialpadButton';

export const rcxCallDialButton = css`
&:not(:hover) {
.rcx-call-dial-button {
display: none !important;
}
}
`;
import { CallTableRow } from './CallTableRow';

const useQuery = (
{
Expand Down Expand Up @@ -66,18 +52,6 @@ const CallTable: FC = () => {
const userIdLoggedIn = Meteor.userId();
const query = useQuery(debouncedParams, debouncedSort, userIdLoggedIn);
const directoryRoute = useRoute('omnichannel-directory');
const isCallReady = useIsCallReady();

const resolveDirectionLabel = useCallback(
(direction: IVoipRoom['direction']) => {
const labels = {
inbound: 'Incoming',
outbound: 'Outgoing',
} as const;
return t(labels[direction] || 'Not_Available');
},
[t],
);

const onHeaderClick = useMutableCallback((id) => {
const [sortBy, sortDirection] = sort;
Expand Down Expand Up @@ -156,34 +130,7 @@ const CallTable: FC = () => {
[sort, onHeaderClick, t],
);

const renderRow = useCallback(
({ _id, fname, callStarted, queue, callDuration, v, direction }) => {
const duration = moment.duration(callDuration / 1000, 'seconds');
const phoneNumber = Array.isArray(v?.phone) ? v?.phone[0]?.phoneNumber : v?.phone;

return (
<Table.Row
key={_id}
className={rcxCallDialButton}
tabIndex={0}
role='link'
onClick={(): void => onRowClick(_id, v?.token)}
action
qa-user-id={_id}
height='40px'
>
<Table.Cell withTruncatedText>{parseOutboundPhoneNumber(fname)}</Table.Cell>
<Table.Cell withTruncatedText>{parseOutboundPhoneNumber(phoneNumber)}</Table.Cell>
<Table.Cell withTruncatedText>{queue}</Table.Cell>
<Table.Cell withTruncatedText>{moment(callStarted).format('L LTS')}</Table.Cell>
<Table.Cell withTruncatedText>{duration.isValid() && duration.humanize()}</Table.Cell>
<Table.Cell withTruncatedText>{resolveDirectionLabel(direction)}</Table.Cell>
<Table.Cell>{isCallReady && <CallDialpadButton phoneNumber={phoneNumber} />}</Table.Cell>
</Table.Row>
);
},
[onRowClick, resolveDirectionLabel, isCallReady],
);
const renderRow = useCallback((room) => <CallTableRow room={room} onRowClick={onRowClick} />, [onRowClick]);

return (
<GenericTable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { IVoipRoom } from '@rocket.chat/core-typings';
import { Table } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import moment from 'moment';
import React, { ReactElement, useCallback } from 'react';

import { parseOutboundPhoneNumber } from '../../../../../ee/client/lib/voip/parseOutboundPhoneNumber';
import { useIsCallReady } from '../../../../contexts/CallContext';
import { CallDialpadButton } from '../CallDialpadButton';

type CallTableRowProps = {
room: IVoipRoom;
onRowClick(_id: string, token?: string): void;
};

export const CallTableRow = ({ room, onRowClick }: CallTableRowProps): ReactElement => {
const t = useTranslation();
const isCallReady = useIsCallReady();

const { _id, fname, callStarted, queue, callDuration = 0, v, direction } = room;
const duration = moment.duration(callDuration / 1000, 'seconds');
const phoneNumber = Array.isArray(v?.phone) ? v?.phone[0]?.phoneNumber : v?.phone;

const resolveDirectionLabel = useCallback(
(direction: IVoipRoom['direction']) => {
const labels = {
inbound: 'Incoming',
outbound: 'Outgoing',
} as const;
return t(labels[direction] || 'Not_Available');
},
[t],
);

return (
<Table.Row
key={_id}
rcx-show-call-button-on-hover
tabIndex={0}
role='link'
onClick={(): void => onRowClick(_id, v?.token)}
action
qa-user-id={_id}
height='40px'
>
<Table.Cell withTruncatedText>{parseOutboundPhoneNumber(fname)}</Table.Cell>
<Table.Cell withTruncatedText>{parseOutboundPhoneNumber(phoneNumber)}</Table.Cell>
<Table.Cell withTruncatedText>{queue}</Table.Cell>
<Table.Cell withTruncatedText>{moment(callStarted).format('L LTS')}</Table.Cell>
<Table.Cell withTruncatedText>{duration.isValid() && duration.humanize()}</Table.Cell>
<Table.Cell withTruncatedText>{resolveDirectionLabel(direction)}</Table.Cell>
<Table.Cell>{isCallReady && <CallDialpadButton phoneNumber={phoneNumber} />}</Table.Cell>
</Table.Row>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { css } from '@rocket.chat/css-in-js';
import { Icon, Pagination, States, StatesAction, StatesActions, StatesIcon, StatesTitle, Box } from '@rocket.chat/fuselage';
import { useDebouncedState, useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
Expand Down Expand Up @@ -27,13 +26,6 @@ type ContactTableProps = {
setContactReload(fn: () => void): void;
};

export const rcxCallDialButton = css`
&:not(:hover) {
.rcx-call-dial-button {
display: none !important;
}
}
`;
function ContactTable({ setContactReload }: ContactTableProps): ReactElement {
const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination();
const { sortBy, sortDirection, setSort } = useSort<'username' | 'phone' | 'name' | 'visitorEmails.address' | 'lastchat'>('username');
Expand Down Expand Up @@ -128,7 +120,7 @@ function ContactTable({ setContactReload }: ContactTableProps): ReactElement {
</GenericTableHeader>
{result.phase === AsyncStatePhase.RESOLVED && (
<GenericTableBody>
{result.value.visitors.map(({ _id, username, fname, visitorEmails, phone, lastChat }) => {
{result.value.visitors.map(({ _id, username, fname, name, visitorEmails, phone, lastChat }) => {
const phoneNumber = phone?.length && phone[0].phoneNumber;
const visitorEmail = visitorEmails?.length && visitorEmails[0].address;

Expand All @@ -140,11 +132,11 @@ function ContactTable({ setContactReload }: ContactTableProps): ReactElement {
role='link'
height='40px'
qa-user-id={_id}
className={rcxCallDialButton}
rcx-show-call-button-on-hover
onClick={onRowClick(_id)}
>
<GenericTableCell withTruncatedText>{username}</GenericTableCell>
<GenericTableCell withTruncatedText>{parseOutboundPhoneNumber(fname)}</GenericTableCell>
<GenericTableCell withTruncatedText>{parseOutboundPhoneNumber(fname || name)}</GenericTableCell>
<GenericTableCell withTruncatedText>{parseOutboundPhoneNumber(phoneNumber)}</GenericTableCell>
<GenericTableCell withTruncatedText>{visitorEmail}</GenericTableCell>
<GenericTableCell withTruncatedText>{lastChat && formatDate(lastChat.ts)}</GenericTableCell>
Expand Down
Loading

0 comments on commit 10932a5

Please sign in to comment.