From a4f8ed4ec4c7a641d45d1331fccb002b346964e8 Mon Sep 17 00:00:00 2001 From: Bharath Balan <62698609+bhabalan@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:32:21 +0530 Subject: [PATCH] fix(task): display dialed number for outdial calls in task widgets Fix issue where outdial calls via dialpad were showing the entrypoint number (ANI) instead of the dialed number in IncomingTask, TaskList, and Task widgets. For outdial calls, the customer participant's DN field now takes precedence over ANI. For inbound calls, ANI is used as before. Changes: - Updated extractIncomingTaskData to check customer participant DN - Updated extractTaskListItemData to check customer participant DN - Added unit tests for outdial and inbound call scenarios - All 633 tests passing Fixes CAI-7359 --- .../task/IncomingTask/incoming-task.utils.tsx | 21 +++- .../task/TaskList/task-list.utils.ts | 21 +++- .../task/IncomingTask/incoming-task.utils.tsx | 101 ++++++++++++++++ .../task/TaskList/task-list.utils.tsx | 112 ++++++++++++++++++ 4 files changed, 253 insertions(+), 2 deletions(-) diff --git a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx index 4da1f8745..5b5a824ef 100644 --- a/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx +++ b/packages/contact-center/cc-components/src/components/task/IncomingTask/incoming-task.utils.tsx @@ -56,7 +56,26 @@ export const extractIncomingTaskData = ( const declineText = !incomingTask.data.wrapUpRequired && isTelephony && isBrowser ? 'Decline' : undefined; // Compute title based on media type - const title = isSocial ? customerName : ani; + // For outdial calls, use the customer participant's DN (dialed number) if available + // For inbound calls, use ANI (caller's number) + let title = isSocial ? customerName : ani; + + if (isTelephony && incomingTask?.data?.interaction?.participants) { + // Find customer participant to get the dialed number for outdial calls + const customerParticipant = Object.values(incomingTask.data.interaction.participants).find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (participant: any) => participant.pType === 'Customer' + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const customerDn = (customerParticipant as any)?.dn; + + // If customer has a DN, use it (this is an outdial call) + // Otherwise, fall back to ANI (inbound call) + if (customerDn) { + title = customerDn; + } + } // Compute disable state for accept button when auto-answering const isAutoAnswering = incomingTask.data.isAutoAnswering || false; diff --git a/packages/contact-center/cc-components/src/components/task/TaskList/task-list.utils.ts b/packages/contact-center/cc-components/src/components/task/TaskList/task-list.utils.ts index aa1591ffc..2b25734b4 100644 --- a/packages/contact-center/cc-components/src/components/task/TaskList/task-list.utils.ts +++ b/packages/contact-center/cc-components/src/components/task/TaskList/task-list.utils.ts @@ -39,7 +39,26 @@ export const extractTaskListItemData = ( const declineText = isTaskIncoming && isTelephony && isBrowser ? 'Decline' : undefined; // Compute title based on media type - const title = isSocial ? customerName : ani; + // For outdial calls, use the customer participant's DN (dialed number) if available + // For inbound calls, use ANI (caller's number) + let title = isSocial ? customerName : ani; + + if (isTelephony && task?.data?.interaction?.participants) { + // Find customer participant to get the dialed number for outdial calls + const customerParticipant = Object.values(task.data.interaction.participants).find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (participant: any) => participant.pType === 'Customer' + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const customerDn = (customerParticipant as any)?.dn; + + // If customer has a DN, use it (this is an outdial call) + // Otherwise, fall back to ANI (inbound call) + if (customerDn) { + title = customerDn; + } + } const isAutoAnswering = task.data.isAutoAnswering || false; diff --git a/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.utils.tsx b/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.utils.tsx index 0198dd810..14c2bf20c 100644 --- a/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.utils.tsx +++ b/packages/contact-center/cc-components/tests/components/task/IncomingTask/incoming-task.utils.tsx @@ -105,6 +105,107 @@ describe('incoming-task.utils', () => { }); }); + describe('Outdial calls', () => { + it('should use customer DN for outdial telephony task', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + + // Set up an outdial scenario with customer participant having DN + mockTask.data.interaction.callAssociatedDetails = { + ani: '+11234567890', // Entrypoint number + customerName: 'Outdial Customer', + virtualTeamName: 'Sales Team', + }; + + mockTask.data.interaction.participants = { + agent1: { + hasJoined: true, + pType: 'Agent', + id: 'agent1', + name: 'Agent Smith', + hasLeft: false, + }, + customer1: { + hasJoined: true, + pType: 'Customer', + id: 'customer1', + name: 'Customer', + dn: '+19876543210', // Dialed number + hasLeft: false, + }, + }; + + const result = extractIncomingTaskData(mockTask, true); + + expect(result.isTelephony).toBe(true); + expect(result.ani).toBe('+11234567890'); + expect(result.title).toBe('+19876543210'); // Should use customer DN, not ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + }); + + it('should fall back to ANI when customer participant has no DN', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + + // Set up an inbound scenario without customer DN + mockTask.data.interaction.callAssociatedDetails = { + ani: '+15551234567', + customerName: 'Inbound Caller', + virtualTeamName: 'Support Team', + }; + + mockTask.data.interaction.participants = { + agent1: { + hasJoined: true, + pType: 'Agent', + id: 'agent1', + name: 'Agent Jones', + hasLeft: false, + }, + customer1: { + hasJoined: true, + pType: 'Customer', + id: 'customer1', + name: 'Customer', + hasLeft: false, + }, + }; + + const result = extractIncomingTaskData(mockTask, true); + + expect(result.isTelephony).toBe(true); + expect(result.ani).toBe('+15551234567'); + expect(result.title).toBe('+15551234567'); // Should fall back to ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + }); + + it('should handle missing participants gracefully', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + + mockTask.data.interaction.callAssociatedDetails = { + ani: '+15559999999', + customerName: 'Test Customer', + }; + + mockTask.data.interaction.participants = undefined; + + const result = extractIncomingTaskData(mockTask, true); + + expect(result.title).toBe('+15559999999'); // Should fall back to ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + }); + }); + describe('Edge cases', () => { it('should handle missing call association details', () => { const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; diff --git a/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.utils.tsx b/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.utils.tsx index ac93a7a84..38285a80d 100644 --- a/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.utils.tsx +++ b/packages/contact-center/cc-components/tests/components/task/TaskList/task-list.utils.tsx @@ -243,6 +243,118 @@ describe('task-list.utils', () => { mockTask.data.interaction.mediaType = originalMediaType; }); }); + + describe('Outdial calls', () => { + it('should use customer DN for outdial telephony task', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + const originalMediaType = mockTask.data.interaction.mediaType; + + // Set up an outdial scenario with customer participant having DN + mockTask.data.interaction.callAssociatedDetails = { + ani: '+11234567890', // Entrypoint number + customerName: 'Outdial Customer', + virtualTeamName: 'Sales Team', + }; + + mockTask.data.interaction.mediaType = MEDIA_CHANNEL.TELEPHONY; + + mockTask.data.interaction.participants = { + agent1: { + hasJoined: true, + pType: 'Agent', + id: 'agent1', + name: 'Agent Smith', + hasLeft: false, + }, + customer1: { + hasJoined: true, + pType: 'Customer', + id: 'customer1', + name: 'Customer', + dn: '+19876543210', // Dialed number + hasLeft: false, + }, + }; + + const result = extractTaskListItemData(mockTask, true, mockTask.data.agentId); + + expect(result.isTelephony).toBe(true); + expect(result.ani).toBe('+11234567890'); + expect(result.title).toBe('+19876543210'); // Should use customer DN, not ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + mockTask.data.interaction.mediaType = originalMediaType; + }); + + it('should fall back to ANI when customer participant has no DN', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + const originalMediaType = mockTask.data.interaction.mediaType; + + // Set up an inbound scenario without customer DN + mockTask.data.interaction.callAssociatedDetails = { + ani: '+15551234567', + customerName: 'Inbound Caller', + virtualTeamName: 'Support Team', + }; + + mockTask.data.interaction.mediaType = MEDIA_CHANNEL.TELEPHONY; + + mockTask.data.interaction.participants = { + agent1: { + hasJoined: true, + pType: 'Agent', + id: 'agent1', + name: 'Agent Jones', + hasLeft: false, + }, + customer1: { + hasJoined: true, + pType: 'Customer', + id: 'customer1', + name: 'Customer', + hasLeft: false, + }, + }; + + const result = extractTaskListItemData(mockTask, true, mockTask.data.agentId); + + expect(result.isTelephony).toBe(true); + expect(result.ani).toBe('+15551234567'); + expect(result.title).toBe('+15551234567'); // Should fall back to ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + mockTask.data.interaction.mediaType = originalMediaType; + }); + + it('should handle missing participants gracefully', () => { + const originalParticipants = mockTask.data.interaction.participants; + const originalCallAssociatedDetails = mockTask.data.interaction.callAssociatedDetails; + const originalMediaType = mockTask.data.interaction.mediaType; + + mockTask.data.interaction.callAssociatedDetails = { + ani: '+15559999999', + customerName: 'Test Customer', + }; + + mockTask.data.interaction.mediaType = MEDIA_CHANNEL.TELEPHONY; + mockTask.data.interaction.participants = undefined; + + const result = extractTaskListItemData(mockTask, true, mockTask.data.agentId); + + expect(result.title).toBe('+15559999999'); // Should fall back to ANI + + // Restore original values + mockTask.data.interaction.participants = originalParticipants; + mockTask.data.interaction.callAssociatedDetails = originalCallAssociatedDetails; + mockTask.data.interaction.mediaType = originalMediaType; + }); + }); }); describe('isTaskSelectable', () => {