Skip to content

Commit 356e3f4

Browse files
feat: External Call History - #41868
1 parent 49ec9a0 commit 356e3f4

58 files changed

Lines changed: 2901 additions & 109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/meteor/client/views/mediaCallHistory/CallHistoryPage.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const getStateFilter = <T extends string[]>(states: T): T | [...T, 'error'] | un
4646
};
4747

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

@@ -64,7 +64,7 @@ type DetailsTab = {
6464

6565
type UserInfoTab = {
6666
openTab: 'user-info';
67-
rid: string;
67+
rid?: string;
6868
userId: string;
6969
};
7070

@@ -96,7 +96,7 @@ const CallHistoryPage = () => {
9696
);
9797

9898
const openUserInfo = useCallback(
99-
(userId: string, rid: string) => {
99+
(userId: string, rid?: string) => {
100100
setTab({ openTab: 'user-info', rid, userId });
101101
},
102102
[setTab],
@@ -216,12 +216,20 @@ const CallHistoryPage = () => {
216216
contact={item.contact}
217217
onClick={() => onClickRow(item.rid ?? '', item._id)}
218218
rid={item.rid ?? ''}
219-
onClickUserInfo={item.rid ? openUserInfo : undefined}
219+
onClickUserInfo={openUserInfo}
220220
/>
221221
);
222222
}
223223

224-
return <CallHistoryRowExternalUser key={item._id} {...item} contact={item.contact} onClick={() => onClickRow('', item._id)} />;
224+
return (
225+
<CallHistoryRowExternalUser
226+
key={item._id}
227+
{...item}
228+
contact={item.contact}
229+
onClick={() => onClickRow('', item._id)}
230+
onClickUserInfo={openUserInfo}
231+
/>
232+
);
225233
})}
226234
</MediaCallHistoryTable>
227235
)}

apps/meteor/client/views/mediaCallHistory/CallHistoryRowExternalUser.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
import { GenericMenu } from '@rocket.chat/ui-client';
22
import type { CallHistoryExternalContact, CallHistoryTableRowProps } from '@rocket.chat/ui-voip';
3-
import { CallHistoryTableRow, usePeekMediaSessionState, useWidgetExternalControls } from '@rocket.chat/ui-voip';
4-
import { useCallback, useMemo } from 'react';
3+
import { CallHistoryTableRow, usePeekMediaSessionState } from '@rocket.chat/ui-voip';
4+
import { useCallback } from 'react';
55
import { useTranslation } from 'react-i18next';
66

7+
import { getItems } from './CallHistoryRowInternalUser';
8+
import { useMediaCallExternalHistoryActions } from './useMediaCallExternalHistoryActions';
9+
710
export type CallHistoryRowExternalUserProps = Omit<CallHistoryTableRowProps<CallHistoryExternalContact>, 'onClick' | 'menu'> & {
811
onClick: (historyId: string) => void;
12+
onClickUserInfo?: (userId: string) => void;
913
};
1014

11-
const CallHistoryRowExternalUser = ({ _id, contact, type, status, duration, timestamp, onClick }: CallHistoryRowExternalUserProps) => {
15+
const CallHistoryRowExternalUser = ({
16+
_id,
17+
contact,
18+
type,
19+
status,
20+
duration,
21+
timestamp,
22+
onClick,
23+
onClickUserInfo,
24+
}: CallHistoryRowExternalUserProps) => {
1225
const { t } = useTranslation();
1326

1427
const state = usePeekMediaSessionState();
15-
const { toggleWidget } = useWidgetExternalControls();
1628

1729
const handleClick = useCallback(() => {
1830
onClick(_id);
1931
}, [onClick, _id]);
2032

21-
const actions = useMemo(() => {
22-
if (state === 'unavailable') {
23-
return [];
24-
}
25-
const disabled = state !== 'available';
26-
return [
27-
{
28-
id: 'voiceCall',
29-
icon: 'phone',
30-
content: t('Voice_call'),
31-
disabled,
32-
tooltip: disabled ? t('Call_in_progress') : undefined,
33-
onClick: () => toggleWidget({ number: contact.number }),
34-
} as const,
35-
];
36-
}, [contact, toggleWidget, t, state]);
33+
const actions = useMediaCallExternalHistoryActions({
34+
contact,
35+
openUserInfo: onClickUserInfo ? (userId) => onClickUserInfo(userId) : undefined,
36+
});
37+
38+
const items = getItems(actions, t, state);
3739

3840
return (
3941
<CallHistoryTableRow
@@ -44,7 +46,7 @@ const CallHistoryRowExternalUser = ({ _id, contact, type, status, duration, time
4446
duration={duration}
4547
timestamp={timestamp}
4648
onClick={handleClick}
47-
menu={<GenericMenu title={t('Options')} items={actions} />}
49+
menu={<GenericMenu title={t('Options')} items={items} />}
4850
/>
4951
);
5052
};

apps/meteor/client/views/mediaCallHistory/CallHistoryRowInternalUser.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const i18nDictionary: Record<HistoryActions, string> = {
3737
userInfo: 'User_info',
3838
} as const;
3939

40-
const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: PeekMediaSessionStateReturn) => {
40+
export const getItems = (actions: HistoryActionCallbacks, t: TFunction, state: PeekMediaSessionStateReturn) => {
4141
return (Object.entries(actions) as [HistoryActions, () => void][])
4242
.filter(([_, callback]) => callback)
4343
.map(([action, callback]) => {

apps/meteor/client/views/mediaCallHistory/MediaCallHistoryContextualbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { callHistoryQueryKeys } from '../../lib/queryKeys';
1818
export type MediaCallHistoryContextualbarProps = {
1919
openRoomId?: string;
2020
messageRoomId?: string;
21-
openUserInfo?: (userId: string, rid: string) => void;
21+
openUserInfo?: (userId: string, rid?: string) => void;
2222
onClose: () => void;
2323
callId?: string;
2424
historyId?: string;
@@ -67,7 +67,7 @@ const MediaCallHistoryContextualbar = ({
6767
}
6868

6969
if (isSuccess && isExternalCallHistoryItem(data)) {
70-
return <MediaCallHistoryExternal onClose={onClose} data={data} />;
70+
return <MediaCallHistoryExternal onClose={onClose} data={data} openUserInfo={openUserInfo} />;
7171
}
7272

7373
return (

apps/meteor/client/views/mediaCallHistory/MediaCallHistoryExternal.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import type { CallHistoryItem, IInternalMediaCallHistoryItem, IMediaCall, Serialized } from '@rocket.chat/core-typings';
2-
import {
3-
CallHistoryContextualBar,
4-
useWidgetExternalControls,
5-
usePeekMediaSessionState,
6-
type CallHistoryExternalContact,
7-
type CallHistoryUnknownContact,
8-
} from '@rocket.chat/ui-voip';
2+
import { CallHistoryContextualBar, type CallHistoryExternalContact, type CallHistoryUnknownContact } from '@rocket.chat/ui-voip';
93
import { useMemo } from 'react';
104

5+
import { useMediaCallExternalHistoryActions } from './useMediaCallExternalHistoryActions';
6+
117
type ExternalCallEndpointData = Serialized<{
128
item: Exclude<CallHistoryItem, IInternalMediaCallHistoryItem>;
139
call?: IMediaCall;
@@ -16,6 +12,7 @@ type ExternalCallEndpointData = Serialized<{
1612
export type MediaCallHistoryExternalProps = {
1713
data: ExternalCallEndpointData;
1814
onClose: () => void;
15+
openUserInfo?: (userId: string) => void;
1916
};
2017

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

25+
const optionalData = {
26+
...(item.contactId && { uid: item.contactId }),
27+
...(item.contactUsername && { username: item.contactUsername }),
28+
};
29+
30+
if (item.contactNumber) {
31+
return {
32+
...optionalData,
33+
number: item.contactNumber,
34+
name: item.contactName,
35+
};
36+
}
37+
38+
if (item.contactName) {
39+
return {
40+
...optionalData,
41+
name: item.contactName,
42+
};
43+
}
44+
2845
return { unknown: true };
2946
};
3047

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

35-
const MediaCallHistoryExternal = ({ data, onClose }: MediaCallHistoryExternalProps) => {
52+
const MediaCallHistoryExternal = ({ data, onClose, openUserInfo }: MediaCallHistoryExternalProps) => {
3653
const contact = useMemo(() => getExternalContact(data.item), [data]);
3754
const historyData = useMemo(() => {
3855
return {
@@ -43,17 +60,11 @@ const MediaCallHistoryExternal = ({ data, onClose }: MediaCallHistoryExternalPro
4360
state: data.item.state,
4461
};
4562
}, [data]);
46-
const state = usePeekMediaSessionState();
47-
const { toggleWidget } = useWidgetExternalControls();
4863

49-
const actions = useMemo(() => {
50-
if (state !== 'available') {
51-
return {};
52-
}
53-
return {
54-
voiceCall: () => toggleWidget(contact),
55-
};
56-
}, [contact, state, toggleWidget]);
64+
const actions = useMediaCallExternalHistoryActions({
65+
contact,
66+
openUserInfo: openUserInfo ? (userId: string) => openUserInfo(userId) : undefined,
67+
});
5768

5869
return <CallHistoryContextualBar onClose={onClose} actions={actions} contact={contact} data={historyData} />;
5970
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
2+
import { useGoToDirectMessage } from '@rocket.chat/ui-client';
3+
import { useUserAvatarPath } from '@rocket.chat/ui-contexts';
4+
import { useWidgetExternalControls, usePeekMediaSessionState } from '@rocket.chat/ui-voip';
5+
import type { CallHistoryExternalContact, CallHistoryUnknownContact } from '@rocket.chat/ui-voip';
6+
import { useMemo } from 'react';
7+
8+
type UseMediaCallExternalHistoryActionsBaseOptions = {
9+
contact: CallHistoryExternalContact | CallHistoryUnknownContact;
10+
openUserInfo?: (userId: string) => void;
11+
};
12+
13+
export const useMediaCallExternalHistoryActions = ({ contact, openUserInfo }: UseMediaCallExternalHistoryActionsBaseOptions) => {
14+
const state = usePeekMediaSessionState();
15+
const { toggleWidget } = useWidgetExternalControls();
16+
17+
const getAvatarUrl = useUserAvatarPath();
18+
19+
const voiceCall = useStableCallback(() => {
20+
if (state !== 'available') {
21+
return;
22+
}
23+
24+
if ('number' in contact && contact.number) {
25+
toggleWidget(contact);
26+
} else if ('uid' in contact && contact.uid && contact.username) {
27+
toggleWidget({
28+
userId: contact.uid,
29+
displayName: contact.name || contact.username || '',
30+
username: contact.username,
31+
avatarUrl: getAvatarUrl({ username: contact.username }),
32+
...(contact.number && { callerId: contact.number }),
33+
});
34+
}
35+
});
36+
37+
const goToDirectMessage = useGoToDirectMessage({ username: 'username' in contact ? contact.username : '' }, '');
38+
39+
const userInfo = useStableCallback(() => {
40+
if (!openUserInfo) {
41+
return;
42+
}
43+
if (!('uid' in contact) || !contact.uid) {
44+
return;
45+
}
46+
openUserInfo(contact.uid);
47+
});
48+
49+
return useMemo(
50+
() => ({
51+
voiceCall: ('number' in contact && contact.number) || ('uid' in contact && contact.uid && contact.username) ? voiceCall : undefined,
52+
directMessage: 'username' in contact && contact.username ? goToDirectMessage : undefined,
53+
userInfo: openUserInfo && 'uid' in contact && contact.uid ? () => userInfo() : undefined,
54+
}),
55+
[voiceCall, goToDirectMessage, openUserInfo, userInfo, contact],
56+
);
57+
};

apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { getUserEmailVerified } from '../../../../lib/utils/getUserEmailVerified
2727
export type UserInfoWithDataProps = {
2828
uid?: IUser['_id'];
2929
username?: IUser['username'];
30-
rid: IRoom['_id'];
30+
rid?: IRoom['_id'];
3131
invitationDate?: string;
3232
onClose: () => void;
3333
onClickBack?: () => void;
@@ -121,7 +121,7 @@ const UserInfoWithData = ({ uid, username, rid, invitationDate, onClose, onClick
121121
<UserInfo
122122
{...user}
123123
invitationDate={invitationDate}
124-
actions={<UserInfoActions user={user} rid={rid} isInvited={Boolean(invitationDate)} backToList={onClickBack} />}
124+
actions={rid ? <UserInfoActions user={user} rid={rid} isInvited={Boolean(invitationDate)} backToList={onClickBack} /> : null}
125125
/>
126126
)}
127127
</ContextualbarDialog>

apps/meteor/ee/server/settings/voip.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,47 @@ export function addSettings(): Promise<void> {
8282
});
8383
});
8484

85+
await this.section('VoIP_TeamCollab_ExternalCallHistory', async function () {
86+
await this.add('VoIP_TeamCollab_ExternalCallHistory_Enabled', false, {
87+
type: 'boolean',
88+
public: true,
89+
invalidValue: false,
90+
i18nDescription: 'VoIP_TeamCollab_ExternalCallHistory_Enabled_Description',
91+
});
92+
93+
const enableQuery = { _id: 'VoIP_TeamCollab_ExternalCallHistory_Enabled', value: true };
94+
95+
await this.add('VoIP_TeamCollab_ExternalCallHistory_Host', '', {
96+
type: 'string',
97+
public: false,
98+
invalidValue: '',
99+
enableQuery,
100+
});
101+
102+
await this.add('VoIP_TeamCollab_ExternalCallHistory_User', '', {
103+
type: 'string',
104+
public: false,
105+
invalidValue: '',
106+
enableQuery,
107+
});
108+
109+
await this.add('VoIP_TeamCollab_ExternalCallHistory_Password', '', {
110+
type: 'password',
111+
public: false,
112+
secret: true,
113+
invalidValue: '',
114+
enableQuery,
115+
});
116+
117+
await this.add('VoIP_TeamCollab_ExternalCallHistory_Timeout', 10000, {
118+
type: 'int',
119+
public: false,
120+
invalidValue: 10000,
121+
enableQuery,
122+
i18nDescription: 'VoIP_TeamCollab_ExternalCallHistory_Timeout_Description',
123+
});
124+
});
125+
85126
await this.section('VoIP_TeamCollab_AdvancedFeatures', async function () {
86127
const enableQuery = { _id: 'Pexip_Integration_Enabled', value: true };
87128

apps/meteor/jest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default {
4545
'<rootDir>/app/utils/lib/**.spec.ts',
4646
'<rootDir>/server/lib/auditServerEvents/**.spec.ts',
4747
'<rootDir>/server/services/import/**/*.spec.ts',
48+
'<rootDir>/server/services/call-history/**/*.spec.ts',
4849
'<rootDir>/server/settings/lib/**.spec.ts',
4950
'<rootDir>/server/cron/**.spec.ts',
5051
'<rootDir>/server/api/*.spec.ts',
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { Logger } from '@rocket.chat/logger';
2+
3+
export const logger = new Logger('CallHistory');

0 commit comments

Comments
 (0)