@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.
- package/dist/services/task/TaskManager.js +9 -1
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/index.js +23 -8
- package/dist/services/task/index.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +1 -1
- package/src/services/task/TaskManager.ts +8 -0
- package/src/services/task/index.ts +31 -9
- package/test/unit/spec/services/task/TaskManager.ts +146 -0
- package/test/unit/spec/services/task/index.ts +58 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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', () => {
|