Skip to content
Merged
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
5 changes: 5 additions & 0 deletions apps/meteor/client/lib/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,8 @@ export const ABACQueryKeys = {
room: (roomId: string) => [...ABACQueryKeys.rooms.all(), roomId] as const,
},
};

export const callHistoryQueryKeys = {
all: ['call-history'] as const,
info: (callId?: string) => [...callHistoryQueryKeys.all, 'info', callId] as const,
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IRoom, IMessage } from '@rocket.chat/core-typings';
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { UiKitContext } from '@rocket.chat/fuselage-ui-kit';
import { useRoomToolbox } from '@rocket.chat/ui-contexts';
import {
useVideoConfDispatchOutgoing,
useVideoConfIsCalling,
Expand Down Expand Up @@ -38,6 +39,8 @@ export const useMessageBlockContextValue = (rid: IRoom['_id'], mid: IMessage['_i

const actionManager = useUiKitActionManager();

const { openTab } = useRoomToolbox();

return {
action: ({ appId, actionId, blockId, value }, event) => {
if (appId === 'videoconf-core') {
Expand All @@ -52,6 +55,12 @@ export const useMessageBlockContextValue = (rid: IRoom['_id'], mid: IMessage['_i
}
}

if (appId === 'media-call-core') {
if (actionId === 'open-history') {
return openTab('media-call-history', blockId);
}
}

actionManager.emitInteraction(appId, {
type: 'blockAction',
actionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
ContextualbarHeader,
ContextualbarIcon,
ContextualbarTitle,
ContextualbarClose,
ContextualbarEmptyContent,
ContextualbarDialog,
ContextualbarSkeleton,
} from '@rocket.chat/ui-client';
import { useEndpoint, useRouteParameter, useRoomToolbox } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';

import MediaCallHistoryExternal, { isExternalCallHistoryItem } from './MediaCallHistoryExternal';
import MediaCallHistoryInternal, { isInternalCallHistoryItem } from './MediaCallHistoryInternal';
import { callHistoryQueryKeys } from '../../lib/queryKeys';

export const MediaCallHistoryContextualbar = () => {
const context = useRouteParameter('context');

const { closeTab } = useRoomToolbox();
const { t } = useTranslation();

const getCallHistory = useEndpoint('GET', '/v1/call-history.info');
const { data, isPending, isSuccess } = useQuery({
queryKey: callHistoryQueryKeys.info(context),
queryFn: async () => {
if (!context) {
throw new Error('Call ID is required');
}
return getCallHistory({ callId: context } as any); // TODO fix this type
},
staleTime: Infinity, // Call history should never change...
enabled: !!context,
});

if (isPending) {
return <ContextualbarSkeleton />;
}

if (isSuccess && isInternalCallHistoryItem(data)) {
return <MediaCallHistoryInternal onClose={closeTab} data={data} />;
}

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

return (
<ContextualbarDialog onClose={closeTab}>
<ContextualbarHeader>
<ContextualbarIcon name='info-circled' />
<ContextualbarTitle>{t('Call_info')}</ContextualbarTitle>
<ContextualbarClose onClick={closeTab} />
</ContextualbarHeader>
<ContextualbarEmptyContent icon='warning' title={t('Call_info_could_not_be_loaded')} subtitle={t('Please_try_again')} />
</ContextualbarDialog>
);
};

export default MediaCallHistoryContextualbar;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { CallHistoryItem, IExternalMediaCallHistoryItem, IMediaCall, Serialized } from '@rocket.chat/core-typings';
import { CallHistoryContextualBar, useMediaCallContext } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

type ExternalCallEndpointData = Serialized<{
item: IExternalMediaCallHistoryItem;
call: IMediaCall;
}>;

type MediaCallHistoryExternalProps = {
data: ExternalCallEndpointData;
onClose: () => void;
};

const getContact = (item: ExternalCallEndpointData['item']) => {
return {
number: item.contactExtension,
};
};

export const isExternalCallHistoryItem = (data: { item: Serialized<CallHistoryItem> }): data is ExternalCallEndpointData => {
return 'external' in data.item && data.item.external;
};

const MediaCallHistoryExternal = ({ data, onClose }: MediaCallHistoryExternalProps) => {
const contact = useMemo(() => getContact(data.item), [data]);
const historyData = useMemo(() => {
return {
callId: data.call._id,
direction: data.item.direction,
duration: data.item.duration,
startedAt: new Date(data.item.ts),
state: data.item.state,
};
}, [data]);
const { onToggleWidget } = useMediaCallContext();

const actions = useMemo(() => {
if (!onToggleWidget) {
return {};
}
return {
voiceCall: () => onToggleWidget(contact),
};
}, [contact, onToggleWidget]);

return <CallHistoryContextualBar onClose={onClose} actions={actions} contact={contact} data={historyData} />;
};

export default MediaCallHistoryExternal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { CallHistoryItem, IInternalMediaCallHistoryItem, IMediaCall, Serialized } from '@rocket.chat/core-typings';
import { CallHistoryContextualBar } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

import { useMediaCallInternalHistoryActions } from './useMediaCallInternalHistoryActions';

type InternalCallEndpointData = Serialized<{
item: IInternalMediaCallHistoryItem;
call: IMediaCall;
}>;

type MediaCallHistoryInternalProps = {
data: InternalCallEndpointData;
onClose: () => void;
};

export const isInternalCallHistoryItem = (data: { item: Serialized<CallHistoryItem> }): data is InternalCallEndpointData => {
return 'external' in data.item && !data.item.external;
};

const getContact = (item: InternalCallEndpointData['item'], call: InternalCallEndpointData['call']) => {
const { caller, callee } = call ?? {};
const contact = caller?.id === item.contactId ? caller : callee;
const { id, sipExtension, username, ...rest } = contact;
return {
...rest,
_id: id,
username: username ?? '',
voiceCallExtension: sipExtension,
};
};

const MediaCallHistoryInternal = ({ data, onClose }: MediaCallHistoryInternalProps) => {
const contact = useMemo(() => getContact(data.item, data.call), [data]);
const historyData = useMemo(() => {
return {
callId: data.call._id,
direction: data.item.direction,
duration: data.item.duration,
startedAt: new Date(data.item.ts),
state: data.item.state,
};
}, [data]);
const actions = useMediaCallInternalHistoryActions(contact, data.item.messageId);

return <CallHistoryContextualBar onClose={onClose} actions={actions} contact={contact} data={historyData} />;
};

export default MediaCallHistoryInternal;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
import type { LocationPathname } from '@rocket.chat/ui-contexts';
import { useCurrentRoutePath, useRoomToolbox, useRouter, useUserAvatarPath } from '@rocket.chat/ui-contexts';
import { useMediaCallContext } from '@rocket.chat/ui-voip';
import { useMemo } from 'react';

import { useRoom } from '../room/contexts/RoomContext';
import { useDirectMessageAction } from '../room/hooks/useUserInfoActions/actions/useDirectMessageAction';

export type InternalCallHistoryContact = {
_id: string;
name?: string;
username: string;
displayName?: string;
voiceCallExtension?: string;
avatarUrl?: string;
};

export const useMediaCallInternalHistoryActions = (contact: InternalCallHistoryContact, messageId?: string) => {
const { onToggleWidget } = useMediaCallContext();
const router = useRouter();

const currentRoutePath = useCurrentRoutePath();
const room = useRoom();
const toolbox = useRoomToolbox();
const getAvatarUrl = useUserAvatarPath();

const voiceCall = useEffectEvent(() => {
if (!onToggleWidget) {
return;
}

onToggleWidget({
userId: contact._id,
displayName: contact.displayName ?? '',
username: contact.username,
avatarUrl: getAvatarUrl({ username: contact.username }),
callerId: contact.voiceCallExtension,
});
});

const directMessage = useDirectMessageAction(contact, room._id);

const jumpToMessage = useEffectEvent(() => {
if (!messageId || !currentRoutePath) {
return;
}

const { msg: _, ...searchParams } = router.getSearchParameters();

router.navigate(
{
pathname: currentRoutePath as LocationPathname,
search: messageId ? { ...searchParams, msg: messageId } : searchParams,
},
{ replace: true },
);
});

const userInfo = useEffectEvent(() => {
toolbox.openTab('user-info', contact._id);
});

return useMemo(
() => ({
voiceCall,
directMessage: directMessage && 'onClick' in directMessage ? directMessage.onClick : undefined,
jumpToMessage,
userInfo,
}),
[voiceCall, directMessage, jumpToMessage, userInfo],
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useRoom } from '../contexts/RoomContext';
import { getRoomGroup } from '../lib/getRoomGroup';
import { useAppsRoomActions } from './hooks/useAppsRoomActions';
import { useCoreRoomActions } from './hooks/useCoreRoomActions';
import { useCoreRoomRoutes } from './hooks/useCoreRoomRoutes';

type RoomToolboxProviderProps = { children: ReactNode };

Expand Down Expand Up @@ -70,6 +71,9 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => {
const coreRoomActions = useCoreRoomActions();
const appsRoomActions = useAppsRoomActions();

// core routes open the contextual bar, but have no button on the header
const coreRoomRoutes = useCoreRoomRoutes();

const allowAnonymousRead = useSetting<boolean>('Accounts_AllowAnonymousRead', false);
const uid = useUserId();

Expand All @@ -89,8 +93,13 @@ const RoomToolboxProvider = ({ children }: RoomToolboxProviderProps) => {
return undefined;
}

const coreRouteTab = coreRoomRoutes.find((route) => route.id === tabActionId);
if (coreRouteTab) {
return coreRouteTab;
}

return actions.find((action) => action.id === tabActionId);
}, [actions, tabActionId]);
}, [coreRoomRoutes, actions, tabActionId]);

const contextValue = useMemo(
(): RoomToolboxContextValue => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { RoomToolboxActionConfig } from '@rocket.chat/ui-contexts';

import MediaCallHistoryContextualbar from '../../../mediaCallHistory/MediaCallHistoryContextualbar';

const mediaCallHistoryRoute: RoomToolboxActionConfig = {
id: 'media-call-history',
title: 'Call_Information',
tabComponent: MediaCallHistoryContextualbar,
icon: 'info-circled',
groups: ['direct'],
};

const coreRoomRoutes = [mediaCallHistoryRoute];

// This isn't really a proper hook, but it could be extended in the future
// So we're maitaning the same pattern as `useCoreRoomActions`
export const useCoreRoomRoutes = (): Array<RoomToolboxActionConfig> => {
return coreRoomRoutes;
};
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@
"Call_Already_Ended": "Call Already Ended",
"Call_ID": "Call ID",
"Call_info": "Call info",
"Call_info_could_not_be_loaded": "Call info could not be loaded",
"Call_Information": "Call Information",
"Call_again": "Call again",
"Call_back": "Call back",
Expand Down Expand Up @@ -4172,6 +4173,7 @@
"Please_select_an_user": "Please select an user",
"Please_select_enabled_yes_or_no": "Please select an option for Enabled",
"Please_select_visibility": "Please select a visibility",
"Please_try_again": "Please try again.",
"Please_wait": "Please wait",
"Please_wait_activation": "Please wait, this can take some time.",
"Please_wait_while_OTR_is_being_established": "Please wait while OTR is being established",
Expand Down
Loading