Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions apps/meteor/client/views/mediaCallHistory/CallHistoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const getStateFilter = <T extends string[]>(states: T): T | [...T, 'error'] | un
};

const getContact = (item: Serialized<CallHistoryItem>): CallHistoryContact => {
if (item.external) {
if (item.type !== 'media-call' || item.external) {
return getExternalContact(item);
}

Expand All @@ -64,7 +64,7 @@ type DetailsTab = {

type UserInfoTab = {
openTab: 'user-info';
rid: string;
rid?: string;
userId: string;
};

Expand Down Expand Up @@ -96,7 +96,7 @@ const CallHistoryPage = () => {
);

const openUserInfo = useCallback(
(userId: string, rid: string) => {
(userId: string, rid?: string) => {
setTab({ openTab: 'user-info', rid, userId });
},
[setTab],
Expand Down Expand Up @@ -216,12 +216,20 @@ const CallHistoryPage = () => {
contact={item.contact}
onClick={() => onClickRow(item.rid ?? '', item._id)}
rid={item.rid ?? ''}
onClickUserInfo={item.rid ? openUserInfo : undefined}
onClickUserInfo={openUserInfo}
/>
);
}

return <CallHistoryRowExternalUser key={item._id} {...item} contact={item.contact} onClick={() => onClickRow('', item._id)} />;
return (
<CallHistoryRowExternalUser
key={item._id}
{...item}
contact={item.contact}
onClick={() => onClickRow('', item._id)}
onClickUserInfo={openUserInfo}
/>
);
})}
</MediaCallHistoryTable>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import { GenericMenu } from '@rocket.chat/ui-client';
import type { CallHistoryExternalContact, CallHistoryTableRowProps } from '@rocket.chat/ui-voip';
import { CallHistoryTableRow, usePeekMediaSessionState, useWidgetExternalControls } from '@rocket.chat/ui-voip';
import { useCallback, useMemo } from 'react';
import { CallHistoryTableRow, usePeekMediaSessionState } from '@rocket.chat/ui-voip';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';

import { getItems } from './CallHistoryRowInternalUser';
import { useMediaCallExternalHistoryActions } from './useMediaCallExternalHistoryActions';

export type CallHistoryRowExternalUserProps = Omit<CallHistoryTableRowProps<CallHistoryExternalContact>, 'onClick' | 'menu'> & {
onClick: (historyId: string) => void;
onClickUserInfo?: (userId: string) => void;
};

const CallHistoryRowExternalUser = ({ _id, contact, type, status, duration, timestamp, onClick }: CallHistoryRowExternalUserProps) => {
const CallHistoryRowExternalUser = ({
_id,
contact,
type,
status,
duration,
timestamp,
onClick,
onClickUserInfo,
}: CallHistoryRowExternalUserProps) => {
const { t } = useTranslation();

const state = usePeekMediaSessionState();
const { toggleWidget } = useWidgetExternalControls();

const handleClick = useCallback(() => {
onClick(_id);
}, [onClick, _id]);

const actions = useMemo(() => {
if (state === 'unavailable') {
return [];
}
const disabled = state !== 'available';
return [
{
id: 'voiceCall',
icon: 'phone',
content: t('Voice_call'),
disabled,
tooltip: disabled ? t('Call_in_progress') : undefined,
onClick: () => toggleWidget({ number: contact.number }),
} as const,
];
}, [contact, toggleWidget, t, state]);
const actions = useMediaCallExternalHistoryActions({
contact,
openUserInfo: onClickUserInfo ? (userId) => onClickUserInfo(userId) : undefined,
});

const items = getItems(actions, t, state);

return (
<CallHistoryTableRow
Expand All @@ -44,7 +46,7 @@ const CallHistoryRowExternalUser = ({ _id, contact, type, status, duration, time
duration={duration}
timestamp={timestamp}
onClick={handleClick}
menu={<GenericMenu title={t('Options')} items={actions} />}
menu={<GenericMenu title={t('Options')} items={items} />}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const i18nDictionary: Record<HistoryActions, string> = {
userInfo: 'User_info',
} as const;

const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: PeekMediaSessionStateReturn) => {
export const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: PeekMediaSessionStateReturn) => {
return (Object.entries(actions) as [HistoryActions, () => void][])
.filter(([_, callback]) => callback)
.map(([action, callback]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { callHistoryQueryKeys } from '../../lib/queryKeys';
export type MediaCallHistoryContextualbarProps = {
openRoomId?: string;
messageRoomId?: string;
openUserInfo?: (userId: string, rid: string) => void;
openUserInfo?: (userId: string, rid?: string) => void;
onClose: () => void;
callId?: string;
historyId?: string;
Expand Down Expand Up @@ -67,7 +67,7 @@ const MediaCallHistoryContextualbar = ({
}

if (isSuccess && isExternalCallHistoryItem(data)) {
return <MediaCallHistoryExternal onClose={onClose} data={data} />;
return <MediaCallHistoryExternal onClose={onClose} data={data} openUserInfo={openUserInfo} />;
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import type { CallHistoryItem, IInternalMediaCallHistoryItem, IMediaCall, Serialized } from '@rocket.chat/core-typings';
import {
CallHistoryContextualBar,
useWidgetExternalControls,
usePeekMediaSessionState,
type CallHistoryExternalContact,
type CallHistoryUnknownContact,
} from '@rocket.chat/ui-voip';
import { CallHistoryContextualBar, type CallHistoryExternalContact, type CallHistoryUnknownContact } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

import { useMediaCallExternalHistoryActions } from './useMediaCallExternalHistoryActions';

type ExternalCallEndpointData = Serialized<{
item: Exclude<CallHistoryItem, IInternalMediaCallHistoryItem>;
call?: IMediaCall;
Expand All @@ -16,6 +12,7 @@ type ExternalCallEndpointData = Serialized<{
export type MediaCallHistoryExternalProps = {
data: ExternalCallEndpointData;
onClose: () => void;
openUserInfo?: (userId: string) => void;
};

export const getExternalContact = (item: ExternalCallEndpointData['item']): CallHistoryExternalContact | CallHistoryUnknownContact => {
Expand All @@ -25,14 +22,34 @@ export const getExternalContact = (item: ExternalCallEndpointData['item']): Call
};
}

const optionalData = {
...(item.contactId && { uid: item.contactId }),
...(item.contactUsername && { username: item.contactUsername }),
};

if (item.contactNumber) {
return {
...optionalData,
number: item.contactNumber,
name: item.contactName,
};
}

if (item.contactName) {
return {
...optionalData,
name: item.contactName,
};
}

return { unknown: true };
};

export const isExternalCallHistoryItem = (data: { item: Serialized<CallHistoryItem> }): data is ExternalCallEndpointData => {
return data.item.type !== 'media-call' || data.item.external;
};

const MediaCallHistoryExternal = ({ data, onClose }: MediaCallHistoryExternalProps) => {
const MediaCallHistoryExternal = ({ data, onClose, openUserInfo }: MediaCallHistoryExternalProps) => {
const contact = useMemo(() => getExternalContact(data.item), [data]);
const historyData = useMemo(() => {
return {
Expand All @@ -43,17 +60,11 @@ const MediaCallHistoryExternal = ({ data, onClose }: MediaCallHistoryExternalPro
state: data.item.state,
};
}, [data]);
const state = usePeekMediaSessionState();
const { toggleWidget } = useWidgetExternalControls();

const actions = useMemo(() => {
if (state !== 'available') {
return {};
}
return {
voiceCall: () => toggleWidget(contact),
};
}, [contact, state, toggleWidget]);
const actions = useMediaCallExternalHistoryActions({
contact,
openUserInfo: openUserInfo ? (userId: string) => openUserInfo(userId) : undefined,
});

return <CallHistoryContextualBar onClose={onClose} actions={actions} contact={contact} data={historyData} />;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { useGoToDirectMessage } from '@rocket.chat/ui-client';
import { useUserAvatarPath } from '@rocket.chat/ui-contexts';
import { useWidgetExternalControls, usePeekMediaSessionState } from '@rocket.chat/ui-voip';
import type { CallHistoryExternalContact, CallHistoryUnknownContact } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

type UseMediaCallExternalHistoryActionsBaseOptions = {
contact: CallHistoryExternalContact | CallHistoryUnknownContact;
openUserInfo?: (userId: string) => void;
};

export const useMediaCallExternalHistoryActions = ({ contact, openUserInfo }: UseMediaCallExternalHistoryActionsBaseOptions) => {
const state = usePeekMediaSessionState();
const { toggleWidget } = useWidgetExternalControls();

const getAvatarUrl = useUserAvatarPath();

const voiceCall = useStableCallback(() => {
if (state !== 'available') {
return;
}

if ('number' in contact && contact.number) {
toggleWidget(contact);
} else if ('uid' in contact && contact.uid && contact.username) {
toggleWidget({
userId: contact.uid,
displayName: contact.name || contact.username || '',
username: contact.username,
avatarUrl: getAvatarUrl({ username: contact.username }),
...(contact.number && { callerId: contact.number }),
});
}
});

const goToDirectMessage = useGoToDirectMessage({ username: 'username' in contact ? contact.username : '' }, '');

const userInfo = useStableCallback(() => {
if (!openUserInfo) {
return;
}
if (!('uid' in contact) || !contact.uid) {
return;
}
openUserInfo(contact.uid);
});

return useMemo(
() => ({
voiceCall: ('number' in contact && contact.number) || ('uid' in contact && contact.uid && contact.username) ? voiceCall : undefined,
directMessage: 'username' in contact && contact.username ? goToDirectMessage : undefined,
userInfo: openUserInfo && 'uid' in contact && contact.uid ? () => userInfo() : undefined,
}),
[voiceCall, goToDirectMessage, openUserInfo, userInfo, contact],
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { getUserEmailVerified } from '../../../../lib/utils/getUserEmailVerified
export type UserInfoWithDataProps = {
uid?: IUser['_id'];
username?: IUser['username'];
rid: IRoom['_id'];
rid?: IRoom['_id'];
invitationDate?: string;
onClose: () => void;
onClickBack?: () => void;
Expand Down Expand Up @@ -121,7 +121,7 @@ const UserInfoWithData = ({ uid, username, rid, invitationDate, onClose, onClick
<UserInfo
{...user}
invitationDate={invitationDate}
actions={<UserInfoActions user={user} rid={rid} isInvited={Boolean(invitationDate)} backToList={onClickBack} />}
actions={rid ? <UserInfoActions user={user} rid={rid} isInvited={Boolean(invitationDate)} backToList={onClickBack} /> : null}
/>
)}
</ContextualbarDialog>
Expand Down
41 changes: 41 additions & 0 deletions apps/meteor/ee/server/settings/voip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,47 @@ export function addSettings(): Promise<void> {
invalidValue: 5060,
});
});

await this.section('VoIP_TeamCollab_ExternalCallHistory', async function () {
await this.add('VoIP_TeamCollab_ExternalCallHistory_Enabled', false, {
type: 'boolean',
public: true,
invalidValue: false,
i18nDescription: 'VoIP_TeamCollab_ExternalCallHistory_Enabled_Description',
});

const enableQuery = { _id: 'VoIP_TeamCollab_ExternalCallHistory_Enabled', value: true };

await this.add('VoIP_TeamCollab_ExternalCallHistory_Host', '', {
type: 'string',
public: false,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_ExternalCallHistory_User', '', {
type: 'string',
public: false,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_ExternalCallHistory_Password', '', {
type: 'password',
public: false,
secret: true,
invalidValue: '',
enableQuery,
});

await this.add('VoIP_TeamCollab_ExternalCallHistory_Timeout', 10000, {
type: 'int',
public: false,
invalidValue: 10000,
enableQuery,
i18nDescription: 'VoIP_TeamCollab_ExternalCallHistory_Timeout_Description',
});
});
},
);
});
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default {
'<rootDir>/app/utils/lib/**.spec.ts',
'<rootDir>/server/lib/auditServerEvents/**.spec.ts',
'<rootDir>/server/services/import/**/*.spec.ts',
'<rootDir>/server/services/call-history/**/*.spec.ts',
'<rootDir>/server/settings/lib/**.spec.ts',
'<rootDir>/server/cron/**.spec.ts',
'<rootDir>/server/api/*.spec.ts',
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/server/services/call-history/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Logger } from '@rocket.chat/logger';

export const logger = new Logger('CallHistory');
23 changes: 23 additions & 0 deletions apps/meteor/server/services/call-history/mitel/definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type MitelConfig = {
host: string;
username: string;
password: string;
timeout?: number;
};

export type MitelCallItem = {
directoryNumber?: string;
name?: string;
callIdentity?: string;
dateTime: Date | null;
timeZone?: string;
duration: number;
typeOfCall?: 'incoming-answered' | 'incoming-missed' | 'outgoing' | 'outgoing-missed';
transferredCall: boolean;
divertedCall: boolean;
firstDialledNumber?: string;
remoteNumber?: string;
directoryNumber2?: string;
name2?: string;
infoText2?: string;
};
Loading
Loading