@webex/contact-center 3.10.0-next.2 → 3.10.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1556,14 +1556,24 @@ export default class Task extends EventEmitter implements ITask {
1556
1556
  * ```
1557
1557
  */
1558
1558
  public async consultConference(): Promise<TaskResponse> {
1559
- // Extract consultation conference data from task data (used in both try and catch)
1560
- const consultationData = {
1561
- agentId: this.agentId,
1562
- destAgentId: this.data.destAgentId,
1563
- destinationType: this.data.destinationType || 'agent',
1564
- };
1565
-
1566
1559
  try {
1560
+ // Get the destination agent ID using custom logic from participants data (same as consultTransfer)
1561
+ const destAgentId = getDestinationAgentId(
1562
+ this.data.interaction?.participants,
1563
+ this.data.agentId
1564
+ );
1565
+
1566
+ // Validate that we have a destination agent (for queue consult scenarios)
1567
+ if (!destAgentId) {
1568
+ throw new Error('No agent has accepted this queue consult yet');
1569
+ }
1570
+ // Extract consultation conference data from task data (used in both try and catch)
1571
+ const consultationData = {
1572
+ agentId: this.agentId,
1573
+ destAgentId,
1574
+ destinationType: this.data.destinationType || 'agent',
1575
+ };
1576
+
1567
1577
  LoggerProxy.info(`Initiating consult conference to ${consultationData.destAgentId}`, {
1568
1578
  module: TASK_FILE,
1569
1579
  method: METHODS.CONSULT_CONFERENCE,
@@ -1611,9 +1621,21 @@ export default class Task extends EventEmitter implements ITask {
1611
1621
  };
1612
1622
 
1613
1623
  // Track failure metrics (following consultTransfer pattern)
1614
- // Build conference data for error tracking using extracted data
1624
+ // Recalculate destination info for error tracking
1625
+ const failedDestAgentId = getDestinationAgentId(
1626
+ this.data.interaction?.participants,
1627
+ this.data.agentId
1628
+ );
1629
+
1630
+ // Build conference data for error tracking using recalculated data
1631
+ const failedConsultationData = {
1632
+ agentId: this.agentId,
1633
+ destAgentId: failedDestAgentId,
1634
+ destinationType: this.data.destinationType || 'agent',
1635
+ };
1636
+
1615
1637
  const failedParamsData = buildConsultConferenceParamData(
1616
- consultationData,
1638
+ failedConsultationData,
1617
1639
  this.data.interactionId
1618
1640
  );
1619
1641
 
@@ -479,6 +479,73 @@ describe('TaskManager', () => {
479
479
  );
480
480
  });
481
481
 
482
+ it('should set isConferenceInProgress correctly when creating task via AGENT_CONTACT with conference in progress', () => {
483
+ const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
484
+ taskManager.setAgentId(testAgentId);
485
+ taskManager.taskCollection = [];
486
+
487
+ const payload = {
488
+ data: {
489
+ ...initalPayload.data,
490
+ type: CC_EVENTS.AGENT_CONTACT,
491
+ interaction: {
492
+ mediaType: 'telephony',
493
+ state: 'conference',
494
+ participants: {
495
+ [testAgentId]: { pType: 'Agent', hasLeft: false },
496
+ 'agent-2': { pType: 'Agent', hasLeft: false },
497
+ 'customer-1': { pType: 'Customer', hasLeft: false },
498
+ },
499
+ media: {
500
+ [taskId]: {
501
+ mType: 'mainCall',
502
+ participants: [testAgentId, 'agent-2', 'customer-1'],
503
+ },
504
+ },
505
+ },
506
+ },
507
+ };
508
+
509
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
510
+
511
+ const createdTask = taskManager.getTask(taskId);
512
+ expect(createdTask).toBeDefined();
513
+ expect(createdTask.data.isConferenceInProgress).toBe(true);
514
+ });
515
+
516
+ it('should set isConferenceInProgress to false when creating task via AGENT_CONTACT with only one agent', () => {
517
+ const testAgentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
518
+ taskManager.setAgentId(testAgentId);
519
+ taskManager.taskCollection = [];
520
+
521
+ const payload = {
522
+ data: {
523
+ ...initalPayload.data,
524
+ type: CC_EVENTS.AGENT_CONTACT,
525
+ interaction: {
526
+ mediaType: 'telephony',
527
+ state: 'connected',
528
+ participants: {
529
+ [testAgentId]: { pType: 'Agent', hasLeft: false },
530
+ 'customer-1': { pType: 'Customer', hasLeft: false },
531
+ },
532
+ media: {
533
+ [taskId]: {
534
+ mType: 'mainCall',
535
+ participants: [testAgentId, 'customer-1'],
536
+ },
537
+ },
538
+ },
539
+ },
540
+ };
541
+
542
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
543
+
544
+ const createdTask = taskManager.getTask(taskId);
545
+ expect(createdTask).toBeDefined();
546
+ expect(createdTask.data.isConferenceInProgress).toBe(false);
547
+ });
548
+
482
549
  it('should emit TASK_END event on AGENT_WRAPUP event', () => {
483
550
  webSocketManagerMock.emit('message', JSON.stringify(initalPayload));
484
551
 
@@ -1438,7 +1505,86 @@ describe('TaskManager', () => {
1438
1505
  // No specific task event emission for participant joined - just data update
1439
1506
  });
1440
1507
 
1508
+ it('should call updateTaskData only once for PARTICIPANT_JOINED_CONFERENCE with pre-calculated isConferenceInProgress', () => {
1509
+ const payload = {
1510
+ data: {
1511
+ type: CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE,
1512
+ interactionId: taskId,
1513
+ participantId: 'new-agent-789',
1514
+ interaction: {
1515
+ participants: {
1516
+ [agentId]: { pType: 'Agent', hasLeft: false },
1517
+ 'agent-2': { pType: 'Agent', hasLeft: false },
1518
+ 'new-agent-789': { pType: 'Agent', hasLeft: false },
1519
+ 'customer-1': { pType: 'Customer', hasLeft: false },
1520
+ },
1521
+ media: {
1522
+ [taskId]: {
1523
+ mType: 'mainCall',
1524
+ participants: [agentId, 'agent-2', 'new-agent-789', 'customer-1'],
1525
+ },
1526
+ },
1527
+ },
1528
+ },
1529
+ };
1530
+
1531
+ const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
1532
+
1533
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
1534
+
1535
+ // Verify updateTaskData was called exactly once
1536
+ expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
1537
+
1538
+ // Verify it was called with isConferenceInProgress already calculated
1539
+ expect(updateTaskDataSpy).toHaveBeenCalledWith(
1540
+ expect.objectContaining({
1541
+ participantId: 'new-agent-789',
1542
+ isConferenceInProgress: true, // 3 active agents
1543
+ })
1544
+ );
1545
+
1546
+ expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
1547
+ });
1548
+
1441
1549
  describe('PARTICIPANT_LEFT_CONFERENCE event handling', () => {
1550
+ it('should call updateTaskData only once for PARTICIPANT_LEFT_CONFERENCE with pre-calculated isConferenceInProgress', () => {
1551
+ const payload = {
1552
+ data: {
1553
+ type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
1554
+ interactionId: taskId,
1555
+ interaction: {
1556
+ participants: {
1557
+ [agentId]: { pType: 'Agent', hasLeft: false },
1558
+ 'agent-2': { pType: 'Agent', hasLeft: true }, // This agent left
1559
+ 'customer-1': { pType: 'Customer', hasLeft: false },
1560
+ },
1561
+ media: {
1562
+ [taskId]: {
1563
+ mType: 'mainCall',
1564
+ participants: [agentId, 'customer-1'], // agent-2 removed from participants
1565
+ },
1566
+ },
1567
+ },
1568
+ },
1569
+ };
1570
+
1571
+ const updateTaskDataSpy = jest.spyOn(task, 'updateTaskData');
1572
+
1573
+ webSocketManagerMock.emit('message', JSON.stringify(payload));
1574
+
1575
+ // Verify updateTaskData was called exactly once
1576
+ expect(updateTaskDataSpy).toHaveBeenCalledTimes(1);
1577
+
1578
+ // Verify it was called with isConferenceInProgress already calculated
1579
+ expect(updateTaskDataSpy).toHaveBeenCalledWith(
1580
+ expect.objectContaining({
1581
+ isConferenceInProgress: false, // Only 1 active agent remains
1582
+ })
1583
+ );
1584
+
1585
+ expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
1586
+ });
1587
+
1442
1588
  it('should emit TASK_PARTICIPANT_LEFT event when participant leaves conference', () => {
1443
1589
  const payload = {
1444
1590
  data: {
@@ -1830,6 +1830,64 @@ describe('Task', () => {
1830
1830
  interactionId: taskId,
1831
1831
  });
1832
1832
  });
1833
+
1834
+ it('should dynamically calculate destAgentId from participants when this.data.destAgentId is null', async () => {
1835
+ // Simulate scenario where destAgentId is not preserved (e.g., after hold/unhold)
1836
+ task.data.destAgentId = null;
1837
+
1838
+ const consultedAgentId = 'consulted-agent-123';
1839
+ getDestinationAgentIdSpy.mockReturnValue(consultedAgentId);
1840
+
1841
+ const mockResponse = {
1842
+ trackingId: 'test-tracking-dynamic',
1843
+ interactionId: taskId,
1844
+ };
1845
+ contactMock.consultConference.mockResolvedValue(mockResponse);
1846
+
1847
+ const result = await task.consultConference();
1848
+
1849
+ // Verify getDestinationAgentId was called to dynamically calculate the destination
1850
+ expect(getDestinationAgentIdSpy).toHaveBeenCalledWith(
1851
+ taskDataMock.interaction?.participants,
1852
+ taskDataMock.agentId
1853
+ );
1854
+
1855
+ // Verify the conference was called with the dynamically calculated destAgentId
1856
+ expect(contactMock.consultConference).toHaveBeenCalledWith({
1857
+ interactionId: taskId,
1858
+ data: {
1859
+ agentId: taskDataMock.agentId,
1860
+ to: consultedAgentId, // Dynamically calculated value
1861
+ destinationType: 'agent',
1862
+ },
1863
+ });
1864
+ expect(result).toEqual(mockResponse);
1865
+ });
1866
+
1867
+ it('should throw error when no destination agent is found (queue consult not accepted)', async () => {
1868
+ // Simulate queue consult scenario where no agent has accepted yet
1869
+ getDestinationAgentIdSpy.mockReturnValue(''); // No agent found
1870
+
1871
+ // Mock generateTaskErrorObject to wrap the error
1872
+ const wrappedError = new Error('Error while performing consultConference');
1873
+ generateTaskErrorObjectSpy.mockReturnValue(wrappedError);
1874
+
1875
+ await expect(task.consultConference()).rejects.toThrow('Error while performing consultConference');
1876
+
1877
+ // Verify the conference was NOT called
1878
+ expect(contactMock.consultConference).not.toHaveBeenCalled();
1879
+
1880
+ // Verify metrics were tracked for the failure
1881
+ expect(mockMetricsManager.trackEvent).toHaveBeenCalledWith(
1882
+ 'Task Conference Start Failed',
1883
+ expect.objectContaining({
1884
+ taskId: taskId,
1885
+ destination: '', // No destination found
1886
+ destinationType: 'agent',
1887
+ }),
1888
+ ['operational', 'behavioral', 'business']
1889
+ );
1890
+ });
1833
1891
  });
1834
1892
 
1835
1893
  describe('exitConference', () => {