@webex/contact-center 3.12.0-task-refactor.7 → 3.12.0-task-refactor.9
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/cc.js +3 -4
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/metrics/constants.js +2 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +74 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/types.js +9 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/task/Task.js +32 -0
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskManager.js +7 -2
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +3 -1
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +76 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/actions.js +113 -23
- package/dist/services/task/state-machine/actions.js.map +1 -1
- package/dist/services/task/state-machine/uiControlsComputer.js +99 -21
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/services/ApiAiAssistant.d.ts +10 -2
- package/dist/types/services/config/types.d.ts +16 -0
- package/dist/types/services/task/Task.d.ts +10 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +110 -0
- package/dist/types/services/task/state-machine/actions.d.ts +2 -0
- package/dist/types/types.d.ts +24 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +1 -1
- package/src/cc.ts +6 -4
- package/src/constants.ts +1 -0
- package/src/metrics/constants.ts +2 -0
- package/src/services/ApiAiAssistant.ts +102 -2
- package/src/services/config/types.ts +8 -0
- package/src/services/task/Task.ts +34 -0
- package/src/services/task/TaskManager.ts +7 -2
- package/src/services/task/TaskUtils.ts +5 -3
- package/src/services/task/ai-docs/AGENTS.md +7 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +12 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +104 -0
- package/src/services/task/state-machine/actions.ts +151 -25
- package/src/services/task/state-machine/uiControlsComputer.ts +173 -30
- package/src/types.ts +25 -0
- package/test/unit/spec/cc.ts +2 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +105 -17
- package/test/unit/spec/services/task/Task.ts +61 -0
- package/test/unit/spec/services/task/TaskManager.ts +42 -0
- package/test/unit/spec/services/task/TaskUtils.ts +65 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +676 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +597 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +99 -106
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
computeUIControls,
|
|
5
5
|
getDefaultUIControls,
|
|
6
6
|
} from '../../../../../../src/services/task/state-machine/uiControlsComputer';
|
|
7
|
+
import {getTaskStateForUiControls} from '../../../../../../src/services/task/state-machine/actions';
|
|
7
8
|
import {TaskContext} from '../../../../../../src/services/task/state-machine/types';
|
|
8
9
|
import {createTaskData} from '../taskTestUtils';
|
|
9
10
|
|
|
@@ -958,6 +959,80 @@ describe('uiControlsComputer consult initiator controls', () => {
|
|
|
958
959
|
expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
|
|
959
960
|
});
|
|
960
961
|
|
|
962
|
+
it('enables consult leg controls for conference DN AgentConsulting after destination joined', () => {
|
|
963
|
+
const taskData = createTaskData({
|
|
964
|
+
agentId: 'agent-1',
|
|
965
|
+
consultingAgentId: 'agent-1',
|
|
966
|
+
consultMediaResourceId: 'consult-media-1',
|
|
967
|
+
isConsulted: false,
|
|
968
|
+
destinationType: 'DN',
|
|
969
|
+
type: 'AgentConsulting' as any,
|
|
970
|
+
interaction: {
|
|
971
|
+
state: 'conference',
|
|
972
|
+
interactionId: 'interaction-1',
|
|
973
|
+
mainInteractionId: 'interaction-1',
|
|
974
|
+
owner: 'agent-1',
|
|
975
|
+
participants: {
|
|
976
|
+
'agent-1': {
|
|
977
|
+
id: 'agent-1',
|
|
978
|
+
pType: 'Agent',
|
|
979
|
+
hasLeft: false,
|
|
980
|
+
consultState: 'consulting',
|
|
981
|
+
isConsulted: false,
|
|
982
|
+
},
|
|
983
|
+
'agent-2': {
|
|
984
|
+
id: 'agent-2',
|
|
985
|
+
pType: 'Agent',
|
|
986
|
+
hasLeft: false,
|
|
987
|
+
consultState: 'conferencing',
|
|
988
|
+
},
|
|
989
|
+
'dn-dest': {
|
|
990
|
+
id: 'dn-dest',
|
|
991
|
+
pType: 'DN',
|
|
992
|
+
hasLeft: false,
|
|
993
|
+
hasJoined: true,
|
|
994
|
+
},
|
|
995
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
|
|
996
|
+
} as any,
|
|
997
|
+
media: {
|
|
998
|
+
'interaction-1': {
|
|
999
|
+
mediaResourceId: 'interaction-1',
|
|
1000
|
+
mType: 'mainCall',
|
|
1001
|
+
participants: ['agent-1', 'agent-2', 'customer-1'],
|
|
1002
|
+
isHold: false,
|
|
1003
|
+
},
|
|
1004
|
+
'consult-media-1': {
|
|
1005
|
+
mediaResourceId: 'consult-media-1',
|
|
1006
|
+
mType: 'consult',
|
|
1007
|
+
participants: ['agent-1', 'dn-dest'],
|
|
1008
|
+
isHold: false,
|
|
1009
|
+
},
|
|
1010
|
+
} as any,
|
|
1011
|
+
} as any,
|
|
1012
|
+
});
|
|
1013
|
+
const baseContext = createVoiceContext();
|
|
1014
|
+
const context = createVoiceContext({
|
|
1015
|
+
consultInitiator: true,
|
|
1016
|
+
consultFromConference: true,
|
|
1017
|
+
consultDestinationAgentJoined: true,
|
|
1018
|
+
consultDestinationType: 'entryPoint',
|
|
1019
|
+
consultCallHeld: false,
|
|
1020
|
+
taskData,
|
|
1021
|
+
uiControlConfig: {
|
|
1022
|
+
...baseContext.uiControlConfig,
|
|
1023
|
+
agentId: 'agent-1',
|
|
1024
|
+
},
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
const uiControls = computeUIControls(TaskState.CONSULTING, context, taskData);
|
|
1028
|
+
|
|
1029
|
+
expect(uiControls.consult.switch).toEqual({isVisible: true, isEnabled: true});
|
|
1030
|
+
expect(uiControls.consult.transfer).toEqual({isVisible: false, isEnabled: false});
|
|
1031
|
+
expect(uiControls.consult.transferConference).toEqual({isVisible: true, isEnabled: true});
|
|
1032
|
+
expect(uiControls.consult.mergeToConference).toEqual({isVisible: true, isEnabled: true});
|
|
1033
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
|
|
1034
|
+
});
|
|
1035
|
+
|
|
961
1036
|
it('keeps transferConference visible on consult leg for initiator even when state is conferencing', () => {
|
|
962
1037
|
const taskData = createConferenceConsultingInitiatorTaskData();
|
|
963
1038
|
const baseContext = createVoiceContext();
|
|
@@ -1137,6 +1212,528 @@ describe('uiControlsComputer consult initiator controls', () => {
|
|
|
1137
1212
|
expect(uiControls.main.exitConference).toEqual({isVisible: false, isEnabled: false});
|
|
1138
1213
|
});
|
|
1139
1214
|
|
|
1215
|
+
function createSimpleHeldConsultInitiatedTaskData() {
|
|
1216
|
+
return createTaskData({
|
|
1217
|
+
agentId: 'agent-1',
|
|
1218
|
+
mediaResourceId: 'interaction-1',
|
|
1219
|
+
consultMediaResourceId: 'consult-media',
|
|
1220
|
+
destAgentId: 'agent-2',
|
|
1221
|
+
destinationType: 'Agent',
|
|
1222
|
+
isConsulted: false,
|
|
1223
|
+
type: 'AgentConsultCreated' as any,
|
|
1224
|
+
interaction: {
|
|
1225
|
+
state: 'consult',
|
|
1226
|
+
interactionId: 'interaction-1',
|
|
1227
|
+
mainInteractionId: 'interaction-1',
|
|
1228
|
+
owner: 'agent-1',
|
|
1229
|
+
participants: {
|
|
1230
|
+
'agent-1': {
|
|
1231
|
+
id: 'agent-1',
|
|
1232
|
+
pType: 'Agent',
|
|
1233
|
+
hasLeft: false,
|
|
1234
|
+
consultState: 'consultInitiated',
|
|
1235
|
+
isConsulted: false,
|
|
1236
|
+
},
|
|
1237
|
+
'agent-2': {
|
|
1238
|
+
id: 'agent-2',
|
|
1239
|
+
pType: 'Agent',
|
|
1240
|
+
hasLeft: false,
|
|
1241
|
+
hasJoined: false,
|
|
1242
|
+
consultState: 'consultReserved',
|
|
1243
|
+
isConsulted: true,
|
|
1244
|
+
},
|
|
1245
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1246
|
+
} as any,
|
|
1247
|
+
media: {
|
|
1248
|
+
'interaction-1': {
|
|
1249
|
+
mediaResourceId: 'interaction-1',
|
|
1250
|
+
mType: 'mainCall',
|
|
1251
|
+
isHold: true,
|
|
1252
|
+
participants: ['customer-1', 'agent-1'],
|
|
1253
|
+
},
|
|
1254
|
+
'consult-media': {
|
|
1255
|
+
mediaResourceId: 'consult-media',
|
|
1256
|
+
mType: 'consult',
|
|
1257
|
+
isHold: false,
|
|
1258
|
+
participants: ['agent-2', 'agent-1'],
|
|
1259
|
+
},
|
|
1260
|
+
} as any,
|
|
1261
|
+
} as any,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function createConsultFailedHeldMainTaskData() {
|
|
1266
|
+
return createTaskData({
|
|
1267
|
+
agentId: 'agent-1',
|
|
1268
|
+
mediaResourceId: 'interaction-1',
|
|
1269
|
+
consultMediaResourceId: 'consult-media',
|
|
1270
|
+
destAgentId: 'agent-2',
|
|
1271
|
+
destinationType: 'Agent',
|
|
1272
|
+
isConsulted: false,
|
|
1273
|
+
type: 'AgentConsultFailed' as any,
|
|
1274
|
+
interaction: {
|
|
1275
|
+
state: 'consult',
|
|
1276
|
+
interactionId: 'interaction-1',
|
|
1277
|
+
mainInteractionId: 'interaction-1',
|
|
1278
|
+
owner: 'agent-1',
|
|
1279
|
+
participants: {
|
|
1280
|
+
'agent-1': {
|
|
1281
|
+
id: 'agent-1',
|
|
1282
|
+
pType: 'Agent',
|
|
1283
|
+
hasLeft: false,
|
|
1284
|
+
consultState: 'consultCompleted',
|
|
1285
|
+
isConsulted: false,
|
|
1286
|
+
},
|
|
1287
|
+
'agent-2': {
|
|
1288
|
+
id: 'agent-2',
|
|
1289
|
+
pType: 'Agent',
|
|
1290
|
+
hasLeft: false,
|
|
1291
|
+
hasJoined: false,
|
|
1292
|
+
consultState: 'consultReserved',
|
|
1293
|
+
isConsulted: true,
|
|
1294
|
+
},
|
|
1295
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1296
|
+
} as any,
|
|
1297
|
+
media: {
|
|
1298
|
+
'interaction-1': {
|
|
1299
|
+
mediaResourceId: 'interaction-1',
|
|
1300
|
+
mType: 'mainCall',
|
|
1301
|
+
isHold: true,
|
|
1302
|
+
participants: ['customer-1', 'agent-1'],
|
|
1303
|
+
},
|
|
1304
|
+
'consult-media': {
|
|
1305
|
+
mediaResourceId: 'consult-media',
|
|
1306
|
+
mType: 'consult',
|
|
1307
|
+
isHold: false,
|
|
1308
|
+
participants: ['agent-2', 'agent-1'],
|
|
1309
|
+
},
|
|
1310
|
+
} as any,
|
|
1311
|
+
} as any,
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
it('infers HELD (not CONSULTING) while consultee has not joined', () => {
|
|
1316
|
+
const taskData = createSimpleHeldConsultInitiatedTaskData();
|
|
1317
|
+
|
|
1318
|
+
expect(getTaskStateForUiControls(taskData as any, 'agent-1')).toBe(TaskState.HELD);
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
it('matches Stable Prod consult-requested controls for AgentConsultCreated while HELD', () => {
|
|
1322
|
+
const taskData = createSimpleHeldConsultInitiatedTaskData();
|
|
1323
|
+
const context = createVoiceContext({
|
|
1324
|
+
taskData: taskData as any,
|
|
1325
|
+
consultInitiator: true,
|
|
1326
|
+
consultDestinationAgentJoined: false,
|
|
1327
|
+
consultCallHeld: false,
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
const uiControls = computeUIControls(TaskState.HELD, context, taskData as any);
|
|
1331
|
+
|
|
1332
|
+
expect(uiControls.activeLeg).toBe('consult');
|
|
1333
|
+
expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: false});
|
|
1334
|
+
expect(uiControls.main.conference).toEqual({isVisible: true, isEnabled: false});
|
|
1335
|
+
expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
|
|
1336
|
+
expect(uiControls.main.hold).toEqual({isVisible: false, isEnabled: false});
|
|
1337
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
|
|
1338
|
+
expect(uiControls.consult.switch).toEqual({isVisible: true, isEnabled: false});
|
|
1339
|
+
expect(uiControls.consult.transfer).toEqual({isVisible: true, isEnabled: false});
|
|
1340
|
+
expect(uiControls.consult.mergeToConference).toEqual({isVisible: true, isEnabled: false});
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it('clears consult leg and restores HELD main controls after AgentConsultEnded from Stable Prod', () => {
|
|
1344
|
+
const taskData = createTaskData({
|
|
1345
|
+
agentId: 'agent-1',
|
|
1346
|
+
mediaResourceId: 'interaction-1',
|
|
1347
|
+
destAgentId: 'agent-2',
|
|
1348
|
+
destinationType: 'Agent',
|
|
1349
|
+
isConsulted: false,
|
|
1350
|
+
type: 'AgentConsultEnded' as any,
|
|
1351
|
+
interaction: {
|
|
1352
|
+
state: 'connected',
|
|
1353
|
+
interactionId: 'interaction-1',
|
|
1354
|
+
mainInteractionId: 'interaction-1',
|
|
1355
|
+
owner: 'agent-1',
|
|
1356
|
+
participants: {
|
|
1357
|
+
'agent-1': {
|
|
1358
|
+
id: 'agent-1',
|
|
1359
|
+
pType: 'Agent',
|
|
1360
|
+
hasLeft: false,
|
|
1361
|
+
consultState: 'consultCompleted',
|
|
1362
|
+
isConsulted: false,
|
|
1363
|
+
},
|
|
1364
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1365
|
+
} as any,
|
|
1366
|
+
media: {
|
|
1367
|
+
'interaction-1': {
|
|
1368
|
+
mediaResourceId: 'interaction-1',
|
|
1369
|
+
mType: 'mainCall',
|
|
1370
|
+
isHold: true,
|
|
1371
|
+
holdTimestamp: 1780495564872,
|
|
1372
|
+
participants: ['customer-1', 'agent-1'],
|
|
1373
|
+
},
|
|
1374
|
+
} as any,
|
|
1375
|
+
} as any,
|
|
1376
|
+
});
|
|
1377
|
+
const staleContext = createVoiceContext({
|
|
1378
|
+
taskData: {
|
|
1379
|
+
...(taskData as any),
|
|
1380
|
+
interaction: {
|
|
1381
|
+
...(taskData as any).interaction,
|
|
1382
|
+
media: {
|
|
1383
|
+
...(taskData as any).interaction.media,
|
|
1384
|
+
'consult-media': {
|
|
1385
|
+
mediaResourceId: 'consult-media',
|
|
1386
|
+
mType: 'consult',
|
|
1387
|
+
isHold: false,
|
|
1388
|
+
participants: ['agent-2', 'agent-1'],
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
},
|
|
1392
|
+
},
|
|
1393
|
+
consultInitiator: true,
|
|
1394
|
+
consultDestinationAgentJoined: true,
|
|
1395
|
+
consultCallHeld: false,
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
const uiControls = computeUIControls(TaskState.CONSULTING, staleContext, taskData as any);
|
|
1399
|
+
|
|
1400
|
+
expect(uiControls.activeLeg).toBe('main');
|
|
1401
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
|
|
1402
|
+
expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
|
|
1403
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1404
|
+
expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
|
|
1405
|
+
expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
it('enables switch, transfer, and merge on consult leg after AgentConsulting accept', () => {
|
|
1409
|
+
const taskData = createTaskData({
|
|
1410
|
+
agentId: 'agent-1',
|
|
1411
|
+
mediaResourceId: 'interaction-1',
|
|
1412
|
+
consultMediaResourceId: 'consult-media',
|
|
1413
|
+
consultingAgentId: 'agent-1',
|
|
1414
|
+
destAgentId: 'agent-2',
|
|
1415
|
+
isConsulted: false,
|
|
1416
|
+
type: 'AgentConsulting' as any,
|
|
1417
|
+
interaction: {
|
|
1418
|
+
state: 'consulting',
|
|
1419
|
+
interactionId: 'interaction-1',
|
|
1420
|
+
mainInteractionId: 'interaction-1',
|
|
1421
|
+
owner: 'agent-1',
|
|
1422
|
+
participants: {
|
|
1423
|
+
'agent-1': {
|
|
1424
|
+
id: 'agent-1',
|
|
1425
|
+
pType: 'Agent',
|
|
1426
|
+
hasLeft: false,
|
|
1427
|
+
consultState: 'consulting',
|
|
1428
|
+
isConsulted: false,
|
|
1429
|
+
},
|
|
1430
|
+
'agent-2': {
|
|
1431
|
+
id: 'agent-2',
|
|
1432
|
+
pType: 'Agent',
|
|
1433
|
+
hasLeft: false,
|
|
1434
|
+
hasJoined: true,
|
|
1435
|
+
consultState: 'consulting',
|
|
1436
|
+
isConsulted: true,
|
|
1437
|
+
},
|
|
1438
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1439
|
+
} as any,
|
|
1440
|
+
media: {
|
|
1441
|
+
'interaction-1': {
|
|
1442
|
+
mediaResourceId: 'interaction-1',
|
|
1443
|
+
mType: 'mainCall',
|
|
1444
|
+
isHold: true,
|
|
1445
|
+
participants: ['customer-1', 'agent-1'],
|
|
1446
|
+
},
|
|
1447
|
+
'consult-media': {
|
|
1448
|
+
mediaResourceId: 'consult-media',
|
|
1449
|
+
mType: 'consult',
|
|
1450
|
+
isHold: false,
|
|
1451
|
+
participants: ['agent-2', 'agent-1'],
|
|
1452
|
+
},
|
|
1453
|
+
} as any,
|
|
1454
|
+
} as any,
|
|
1455
|
+
});
|
|
1456
|
+
const context = createVoiceContext({
|
|
1457
|
+
taskData: taskData as any,
|
|
1458
|
+
consultInitiator: true,
|
|
1459
|
+
consultDestinationAgentJoined: true,
|
|
1460
|
+
consultCallHeld: false,
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
const uiControls = computeUIControls(TaskState.CONSULTING, context, taskData as any);
|
|
1464
|
+
|
|
1465
|
+
expect(uiControls.activeLeg).toBe('consult');
|
|
1466
|
+
expect(uiControls.consult.switch).toEqual({isVisible: true, isEnabled: true});
|
|
1467
|
+
expect(uiControls.consult.transfer).toEqual({isVisible: true, isEnabled: true});
|
|
1468
|
+
expect(uiControls.consult.mergeToConference).toEqual({isVisible: true, isEnabled: true});
|
|
1469
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
it('shows RONA failure controls after AgentConsultFailed while HELD', () => {
|
|
1473
|
+
const taskData = createConsultFailedHeldMainTaskData();
|
|
1474
|
+
const context = createVoiceContext({
|
|
1475
|
+
taskData: taskData as any,
|
|
1476
|
+
consultInitiator: false,
|
|
1477
|
+
consultDestinationAgentJoined: false,
|
|
1478
|
+
consultCallHeld: false,
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
const uiControls = computeUIControls(TaskState.HELD, context, taskData as any);
|
|
1482
|
+
|
|
1483
|
+
expect(uiControls.activeLeg).toBe('main');
|
|
1484
|
+
expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
|
|
1485
|
+
expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
|
|
1486
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1487
|
+
expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
|
|
1488
|
+
expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
|
|
1489
|
+
expect(uiControls.main.conference).toEqual({isVisible: false, isEnabled: false});
|
|
1490
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
it('enables main.consult on AgentConsultFailed while consult media remains and destinationJoined is stale', () => {
|
|
1494
|
+
const taskData = createConsultFailedHeldMainTaskData();
|
|
1495
|
+
const context = createVoiceContext({
|
|
1496
|
+
taskData: taskData as any,
|
|
1497
|
+
consultInitiator: true,
|
|
1498
|
+
consultDestinationAgentJoined: true,
|
|
1499
|
+
consultCallHeld: false,
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
const uiControls = computeUIControls(TaskState.HELD, context, taskData as any);
|
|
1503
|
+
|
|
1504
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
it('enables main.consult when consultCompleted with stale consultMediaResourceId only (RONA cleanup)', () => {
|
|
1508
|
+
const taskData = createTaskData({
|
|
1509
|
+
agentId: 'agent-1',
|
|
1510
|
+
mediaResourceId: 'interaction-1',
|
|
1511
|
+
consultMediaResourceId: 'consult-media',
|
|
1512
|
+
isConsulted: false,
|
|
1513
|
+
type: 'AgentConsultEnded' as any,
|
|
1514
|
+
interaction: {
|
|
1515
|
+
state: 'connected',
|
|
1516
|
+
interactionId: 'interaction-1',
|
|
1517
|
+
mainInteractionId: 'interaction-1',
|
|
1518
|
+
owner: 'agent-1',
|
|
1519
|
+
participants: {
|
|
1520
|
+
'agent-1': {
|
|
1521
|
+
id: 'agent-1',
|
|
1522
|
+
pType: 'Agent',
|
|
1523
|
+
hasLeft: false,
|
|
1524
|
+
consultState: 'consultCompleted',
|
|
1525
|
+
isConsulted: false,
|
|
1526
|
+
},
|
|
1527
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1528
|
+
} as any,
|
|
1529
|
+
media: {
|
|
1530
|
+
'interaction-1': {
|
|
1531
|
+
mediaResourceId: 'interaction-1',
|
|
1532
|
+
mType: 'mainCall',
|
|
1533
|
+
isHold: true,
|
|
1534
|
+
participants: ['customer-1', 'agent-1'],
|
|
1535
|
+
},
|
|
1536
|
+
} as any,
|
|
1537
|
+
} as any,
|
|
1538
|
+
});
|
|
1539
|
+
const context = createVoiceContext({
|
|
1540
|
+
taskData: taskData as any,
|
|
1541
|
+
consultInitiator: true,
|
|
1542
|
+
consultDestinationAgentJoined: true,
|
|
1543
|
+
consultCallHeld: false,
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
const uiControls = computeUIControls(TaskState.CONSULT_INITIATING, context, taskData as any);
|
|
1547
|
+
|
|
1548
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it('enables main.consult after AgentConsultFailed then AgentConsultEnded on held main leg (RONA)', () => {
|
|
1552
|
+
const consultEndedTaskData = createTaskData({
|
|
1553
|
+
agentId: 'agent-1',
|
|
1554
|
+
mediaResourceId: 'interaction-1',
|
|
1555
|
+
consultMediaResourceId: 'consult-media',
|
|
1556
|
+
destAgentId: 'agent-2',
|
|
1557
|
+
destinationType: 'Agent',
|
|
1558
|
+
isConsulted: false,
|
|
1559
|
+
type: 'AgentConsultEnded' as any,
|
|
1560
|
+
interaction: {
|
|
1561
|
+
state: 'connected',
|
|
1562
|
+
interactionId: 'interaction-1',
|
|
1563
|
+
mainInteractionId: 'interaction-1',
|
|
1564
|
+
owner: 'agent-1',
|
|
1565
|
+
participants: {
|
|
1566
|
+
'agent-1': {
|
|
1567
|
+
id: 'agent-1',
|
|
1568
|
+
pType: 'Agent',
|
|
1569
|
+
hasLeft: false,
|
|
1570
|
+
consultState: 'consultCompleted',
|
|
1571
|
+
isConsulted: false,
|
|
1572
|
+
},
|
|
1573
|
+
'agent-2': {
|
|
1574
|
+
id: 'agent-2',
|
|
1575
|
+
pType: 'Agent',
|
|
1576
|
+
hasLeft: false,
|
|
1577
|
+
hasJoined: false,
|
|
1578
|
+
consultState: 'consultReserved',
|
|
1579
|
+
isConsulted: true,
|
|
1580
|
+
},
|
|
1581
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1582
|
+
} as any,
|
|
1583
|
+
media: {
|
|
1584
|
+
'interaction-1': {
|
|
1585
|
+
mediaResourceId: 'interaction-1',
|
|
1586
|
+
mType: 'mainCall',
|
|
1587
|
+
isHold: true,
|
|
1588
|
+
participants: ['customer-1', 'agent-1'],
|
|
1589
|
+
},
|
|
1590
|
+
} as any,
|
|
1591
|
+
} as any,
|
|
1592
|
+
});
|
|
1593
|
+
const staleContext = createVoiceContext({
|
|
1594
|
+
taskData: consultEndedTaskData as any,
|
|
1595
|
+
consultInitiator: true,
|
|
1596
|
+
consultDestinationAgentJoined: false,
|
|
1597
|
+
consultCallHeld: false,
|
|
1598
|
+
consultFromConference: false,
|
|
1599
|
+
});
|
|
1600
|
+
|
|
1601
|
+
const uiControls = computeUIControls(TaskState.HELD, staleContext, consultEndedTaskData as any);
|
|
1602
|
+
|
|
1603
|
+
expect(uiControls.activeLeg).toBe('main');
|
|
1604
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1605
|
+
expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
|
|
1606
|
+
expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
it('enables main.consult after AgentConsultEnded when consult ended before consultee answered (held main leg)', () => {
|
|
1610
|
+
// Agent 1 initiates consult to Agent 2 and ends it before Agent 2 answers.
|
|
1611
|
+
// Backend AgentConsultEnded: main on hold, self consultCompleted, no consult media,
|
|
1612
|
+
// and the consultee (destAgent) is not present in the participants map.
|
|
1613
|
+
const consultEndedTaskData = createTaskData({
|
|
1614
|
+
agentId: 'agent-1',
|
|
1615
|
+
mediaResourceId: 'interaction-1',
|
|
1616
|
+
consultMediaResourceId: 'consult-media',
|
|
1617
|
+
destAgentId: 'agent-2',
|
|
1618
|
+
destinationType: 'Agent',
|
|
1619
|
+
isConsulted: false,
|
|
1620
|
+
type: 'AgentConsultEnded' as any,
|
|
1621
|
+
interaction: {
|
|
1622
|
+
state: 'connected',
|
|
1623
|
+
interactionId: 'interaction-1',
|
|
1624
|
+
mainInteractionId: 'interaction-1',
|
|
1625
|
+
owner: 'agent-1',
|
|
1626
|
+
participants: {
|
|
1627
|
+
'agent-1': {
|
|
1628
|
+
id: 'agent-1',
|
|
1629
|
+
pType: 'Agent',
|
|
1630
|
+
hasLeft: false,
|
|
1631
|
+
hasJoined: true,
|
|
1632
|
+
consultState: 'consultCompleted',
|
|
1633
|
+
isConsulted: false,
|
|
1634
|
+
},
|
|
1635
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1636
|
+
} as any,
|
|
1637
|
+
media: {
|
|
1638
|
+
'interaction-1': {
|
|
1639
|
+
mediaResourceId: 'interaction-1',
|
|
1640
|
+
mType: 'mainCall',
|
|
1641
|
+
isHold: true,
|
|
1642
|
+
participants: ['customer-1', 'agent-1'],
|
|
1643
|
+
},
|
|
1644
|
+
} as any,
|
|
1645
|
+
} as any,
|
|
1646
|
+
});
|
|
1647
|
+
// Stale context from CONSULT_INITIATING: initiator true, consultee never joined.
|
|
1648
|
+
const staleContext = createVoiceContext({
|
|
1649
|
+
taskData: consultEndedTaskData as any,
|
|
1650
|
+
consultInitiator: true,
|
|
1651
|
+
consultDestinationAgentJoined: false,
|
|
1652
|
+
consultCallHeld: false,
|
|
1653
|
+
consultFromConference: false,
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
const uiControls = computeUIControls(TaskState.HELD, staleContext, consultEndedTaskData as any);
|
|
1657
|
+
|
|
1658
|
+
expect(uiControls.activeLeg).toBe('main');
|
|
1659
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1660
|
+
expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
|
|
1661
|
+
expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
|
|
1662
|
+
expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
|
|
1663
|
+
expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
|
|
1664
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
it('enables main.consult after AgentConsultEnded even when reconciled task.data still carries stale consult media/participant', () => {
|
|
1668
|
+
// Reproduces the real runtime data: Task.reconcileData deep-merges and never deletes keys,
|
|
1669
|
+
// so after AgentConsultEnded the stale consult-media entry and consultee participant from
|
|
1670
|
+
// AgentConsultCreated persist in task.data. The consult button must still be enabled.
|
|
1671
|
+
const consultEndedTaskData = createTaskData({
|
|
1672
|
+
agentId: 'agent-1',
|
|
1673
|
+
mediaResourceId: 'interaction-1',
|
|
1674
|
+
consultMediaResourceId: 'consult-media',
|
|
1675
|
+
destAgentId: 'agent-2',
|
|
1676
|
+
destinationType: 'Agent',
|
|
1677
|
+
isConsulted: false,
|
|
1678
|
+
type: 'AgentConsultEnded' as any,
|
|
1679
|
+
interaction: {
|
|
1680
|
+
state: 'connected',
|
|
1681
|
+
interactionId: 'interaction-1',
|
|
1682
|
+
mainInteractionId: 'interaction-1',
|
|
1683
|
+
owner: 'agent-1',
|
|
1684
|
+
participants: {
|
|
1685
|
+
'agent-1': {
|
|
1686
|
+
id: 'agent-1',
|
|
1687
|
+
pType: 'Agent',
|
|
1688
|
+
hasLeft: false,
|
|
1689
|
+
hasJoined: true,
|
|
1690
|
+
consultState: 'consultCompleted',
|
|
1691
|
+
isConsulted: false,
|
|
1692
|
+
},
|
|
1693
|
+
'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
|
|
1694
|
+
// Stale consultee retained by reconcileData (never joined, consult leg gone).
|
|
1695
|
+
'agent-2': {
|
|
1696
|
+
id: 'agent-2',
|
|
1697
|
+
pType: 'Agent',
|
|
1698
|
+
hasLeft: false,
|
|
1699
|
+
hasJoined: false,
|
|
1700
|
+
consultState: 'consulting',
|
|
1701
|
+
isConsulted: true,
|
|
1702
|
+
},
|
|
1703
|
+
} as any,
|
|
1704
|
+
media: {
|
|
1705
|
+
'interaction-1': {
|
|
1706
|
+
mediaResourceId: 'interaction-1',
|
|
1707
|
+
mType: 'mainCall',
|
|
1708
|
+
isHold: true,
|
|
1709
|
+
participants: ['customer-1', 'agent-1'],
|
|
1710
|
+
},
|
|
1711
|
+
// Stale consult media retained by reconcileData merge.
|
|
1712
|
+
'consult-media': {
|
|
1713
|
+
mediaResourceId: 'consult-media',
|
|
1714
|
+
mType: 'consult',
|
|
1715
|
+
isHold: false,
|
|
1716
|
+
participants: ['agent-1', 'agent-2'],
|
|
1717
|
+
},
|
|
1718
|
+
} as any,
|
|
1719
|
+
} as any,
|
|
1720
|
+
});
|
|
1721
|
+
const staleContext = createVoiceContext({
|
|
1722
|
+
taskData: consultEndedTaskData as any,
|
|
1723
|
+
consultInitiator: true,
|
|
1724
|
+
consultDestinationAgentJoined: false,
|
|
1725
|
+
consultCallHeld: false,
|
|
1726
|
+
consultFromConference: false,
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
const uiControls = computeUIControls(TaskState.HELD, staleContext, consultEndedTaskData as any);
|
|
1730
|
+
|
|
1731
|
+
expect(uiControls.activeLeg).toBe('main');
|
|
1732
|
+
expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
|
|
1733
|
+
expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
|
|
1734
|
+
expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1140
1737
|
});
|
|
1141
1738
|
|
|
1142
1739
|
describe('uiControlsComputer outdial accept/decline controls', () => {
|