diff --git a/.changeset/strong-maps-act.md b/.changeset/strong-maps-act.md new file mode 100644 index 0000000000000..04c8c2c6c64ae --- /dev/null +++ b/.changeset/strong-maps-act.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/ui-voip": minor +--- + +Introduces an info button to voice call's in-chat history message, which opens a contextual bar with more detailed information about the voice call. diff --git a/apps/meteor/server/services/media-call/service.ts b/apps/meteor/server/services/media-call/service.ts index 39d29f66714fe..090a5260af885 100644 --- a/apps/meteor/server/services/media-call/service.ts +++ b/apps/meteor/server/services/media-call/service.ts @@ -198,7 +198,7 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall const state = this.getCallHistoryItemState(call); const duration = this.getCallDuration(call); - const record = getHistoryMessagePayload(state, duration); + const record = getHistoryMessagePayload(state, duration, call._id); try { const message = await sendMessage(user, record, room, false); diff --git a/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.spec.ts b/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.spec.ts index 1184e5b535612..d73afafd48a0c 100644 --- a/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.spec.ts +++ b/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.spec.ts @@ -1,4 +1,10 @@ -import { callStateToTranslationKey, callStateToIcon, getFormattedCallDuration, getHistoryMessagePayload } from './getHistoryMessagePayload'; +import { + callStateToTranslationKey, + callStateToIcon, + getFormattedCallDuration, + getHistoryMessagePayload, + getHistoryAction, +} from './getHistoryMessagePayload'; const appId = 'media-call-core'; describe('callStateToTranslationKey', () => { @@ -31,27 +37,27 @@ describe('callStateToTranslationKey', () => { describe('callStateToIcon', () => { it('should return correct icon for "ended" state', () => { const result = callStateToIcon('ended'); - expect(result).toEqual({ type: 'icon', icon: 'phone-off', variant: 'secondary' }); + expect(result).toEqual({ type: 'icon', icon: 'phone-off', variant: 'secondary', framed: true }); }); it('should return correct icon for "not-answered" state', () => { const result = callStateToIcon('not-answered'); - expect(result).toEqual({ type: 'icon', icon: 'clock', variant: 'danger' }); + expect(result).toEqual({ type: 'icon', icon: 'clock', variant: 'danger', framed: true }); }); it('should return correct icon for "failed" state', () => { const result = callStateToIcon('failed'); - expect(result).toEqual({ type: 'icon', icon: 'phone-issue', variant: 'danger' }); + expect(result).toEqual({ type: 'icon', icon: 'phone-issue', variant: 'danger', framed: true }); }); it('should return correct icon for "error" state', () => { const result = callStateToIcon('error'); - expect(result).toEqual({ type: 'icon', icon: 'phone-issue', variant: 'danger' }); + expect(result).toEqual({ type: 'icon', icon: 'phone-issue', variant: 'danger', framed: true }); }); it('should return correct icon for "transferred" state', () => { const result = callStateToIcon('transferred'); - expect(result).toEqual({ type: 'icon', icon: 'arrow-forward', variant: 'secondary' }); + expect(result).toEqual({ type: 'icon', icon: 'arrow-forward', variant: 'secondary', framed: true }); }); }); @@ -102,9 +108,24 @@ describe('getFormattedCallDuration', () => { }); }); +const actionObj = { + type: 'icon_button', + icon: { type: 'icon', icon: 'info', variant: 'default' }, + actionId: 'open-history', + appId, + blockId: 'callid', +}; + +describe('getHistoryAction', () => { + it('should return correct action for "ended" state', () => { + const result = getHistoryAction('callid'); + expect(result).toEqual(actionObj); + }); +}); + describe('getHistoryMessagePayload', () => { it('should return correct payload for "ended" state without duration', () => { - const result = getHistoryMessagePayload('ended', undefined); + const result = getHistoryMessagePayload('ended', undefined, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -116,9 +137,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'phone-off', variant: 'secondary' }, + { type: 'icon', icon: 'phone-off', variant: 'secondary', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_ended_bold' }, text: 'Call ended' }, ], + action: actionObj, }, ], }, @@ -127,7 +149,7 @@ describe('getHistoryMessagePayload', () => { }); it('should return correct payload for "ended" state with duration', () => { - const result = getHistoryMessagePayload('ended', 125); + const result = getHistoryMessagePayload('ended', 125, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -139,9 +161,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'phone-off', variant: 'secondary' }, + { type: 'icon', icon: 'phone-off', variant: 'secondary', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_ended_bold' }, text: 'Call ended' }, ], + action: actionObj, }, { background: 'secondary', @@ -154,7 +177,7 @@ describe('getHistoryMessagePayload', () => { }); it('should return correct payload for "not-answered" state', () => { - const result = getHistoryMessagePayload('not-answered', undefined); + const result = getHistoryMessagePayload('not-answered', undefined, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -166,9 +189,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'clock', variant: 'danger' }, + { type: 'icon', icon: 'clock', variant: 'danger', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_not_answered_bold' }, text: 'Call not answered' }, ], + action: actionObj, }, ], }, @@ -177,7 +201,7 @@ describe('getHistoryMessagePayload', () => { }); it('should return correct payload for "failed" state', () => { - const result = getHistoryMessagePayload('failed', undefined); + const result = getHistoryMessagePayload('failed', undefined, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -189,9 +213,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'phone-issue', variant: 'danger' }, + { type: 'icon', icon: 'phone-issue', variant: 'danger', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_failed_bold' }, text: 'Call failed' }, ], + action: actionObj, }, ], }, @@ -200,7 +225,7 @@ describe('getHistoryMessagePayload', () => { }); it('should return correct payload for "error" state', () => { - const result = getHistoryMessagePayload('error', undefined); + const result = getHistoryMessagePayload('error', undefined, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -212,9 +237,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'phone-issue', variant: 'danger' }, + { type: 'icon', icon: 'phone-issue', variant: 'danger', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_failed_bold' }, text: 'Call failed' }, ], + action: actionObj, }, ], }, @@ -223,7 +249,7 @@ describe('getHistoryMessagePayload', () => { }); it('should return correct payload for "transferred" state', () => { - const result = getHistoryMessagePayload('transferred', undefined); + const result = getHistoryMessagePayload('transferred', undefined, 'callid'); expect(result).toEqual({ msg: '', groupable: false, @@ -235,9 +261,10 @@ describe('getHistoryMessagePayload', () => { { background: 'default', elements: [ - { type: 'icon', icon: 'arrow-forward', variant: 'secondary' }, + { type: 'icon', icon: 'arrow-forward', variant: 'secondary', framed: true }, { type: 'mrkdwn', i18n: { key: 'Call_transferred_bold' }, text: 'Call transferred' }, ], + action: actionObj, }, ], }, @@ -246,7 +273,7 @@ describe('getHistoryMessagePayload', () => { }); it('should include duration row when duration is provided', () => { - const result = getHistoryMessagePayload('ended', 3665); + const result = getHistoryMessagePayload('ended', 3665, 'callid'); expect(result.blocks[0].rows).toHaveLength(2); expect(result.blocks[0].rows[1]).toEqual({ @@ -256,7 +283,7 @@ describe('getHistoryMessagePayload', () => { }); it('should not include duration row when duration is undefined', () => { - const result = getHistoryMessagePayload('ended', undefined); + const result = getHistoryMessagePayload('ended', undefined, 'callid'); expect(result.blocks[0].rows).toHaveLength(1); }); @@ -265,7 +292,7 @@ describe('getHistoryMessagePayload', () => { const duration = 125; states.forEach((state) => { - const result = getHistoryMessagePayload(state, duration); + const result = getHistoryMessagePayload(state, duration, 'callid'); expect(result.msg).toBe(''); expect(result.groupable).toBe(false); expect(result.blocks).toHaveLength(1); @@ -273,6 +300,8 @@ describe('getHistoryMessagePayload', () => { expect(result.blocks[0].rows).toHaveLength(2); expect(result.blocks[0].rows[1].background).toBe('secondary'); expect(result.blocks[0].rows[1].elements[0].type).toBe('mrkdwn'); + expect(result.blocks[0].rows[0].action).toEqual(actionObj); + expect(result.blocks[0].rows[1].action).toBeUndefined(); }); }); }); diff --git a/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.ts b/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.ts index 27289904fcdd5..1b854a4460d34 100644 --- a/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.ts +++ b/packages/ui-voip/src/ui-kit/getHistoryMessagePayload.ts @@ -1,5 +1,5 @@ import type { CallHistoryItemState, IMessage } from '@rocket.chat/core-typings'; -import type { IconElement, InfoCardBlock, TextObject } from '@rocket.chat/ui-kit'; +import type { IconButtonElement, FrameableIconElement, InfoCardBlock, TextObject } from '@rocket.chat/ui-kit'; import { intervalToDuration, secondsToMilliseconds } from 'date-fns'; const APP_ID = 'media-call-core'; @@ -18,17 +18,17 @@ export const callStateToTranslationKey = (callState: CallHistoryItemState): Text } }; -export const callStateToIcon = (callState: CallHistoryItemState): IconElement => { +export const callStateToIcon = (callState: CallHistoryItemState): FrameableIconElement => { switch (callState) { case 'ended': - return { type: 'icon', icon: 'phone-off', variant: 'secondary' }; + return { type: 'icon', icon: 'phone-off', variant: 'secondary', framed: true }; case 'not-answered': - return { type: 'icon', icon: 'clock', variant: 'danger' }; + return { type: 'icon', icon: 'clock', variant: 'danger', framed: true }; case 'failed': case 'error': - return { type: 'icon', icon: 'phone-issue', variant: 'danger' }; + return { type: 'icon', icon: 'phone-issue', variant: 'danger', framed: true }; case 'transferred': - return { type: 'icon', icon: 'arrow-forward', variant: 'secondary' }; + return { type: 'icon', icon: 'arrow-forward', variant: 'secondary', framed: true }; } }; @@ -54,9 +54,20 @@ export const getFormattedCallDuration = (callDuration: number | undefined): Text } as const; }; +export const getHistoryAction = (callId: string): IconButtonElement => { + return { + type: 'icon_button', + icon: { type: 'icon', icon: 'info', variant: 'default' }, + actionId: 'open-history', + appId: APP_ID, + blockId: callId, + }; +}; + export const getHistoryMessagePayload = ( callState: CallHistoryItemState, callDuration: number | undefined, + callId?: string, ): Pick & { blocks: [InfoCardBlock] } => { const callStateTranslationKey = callStateToTranslationKey(callState); const icon = callStateToIcon(callState); @@ -73,6 +84,7 @@ export const getHistoryMessagePayload = ( { background: 'default', elements: [icon, callStateTranslationKey], + ...(callId && { action: getHistoryAction(callId) }), }, ...(callDurationFormatted ? [ diff --git a/packages/ui-voip/src/views/CallHistoryContextualbar/__snapshots__/CallHistoryContextualbar.spec.tsx.snap b/packages/ui-voip/src/views/CallHistoryContextualbar/__snapshots__/CallHistoryContextualbar.spec.tsx.snap index 85936952ebd7c..ca0021e33c851 100644 --- a/packages/ui-voip/src/views/CallHistoryContextualbar/__snapshots__/CallHistoryContextualbar.spec.tsx.snap +++ b/packages/ui-voip/src/views/CallHistoryContextualbar/__snapshots__/CallHistoryContextualbar.spec.tsx.snap @@ -176,7 +176,7 @@ exports[`renders Default without crashing 1`] = ` > @@ -485,7 +485,7 @@ exports[`renders ExternalContact without crashing 1`] = ` >