@webex/contact-center 3.9.0-next.25 → 3.9.0-next.27
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 +1 -1
- package/dist/cc.js.map +1 -1
- package/dist/services/task/TaskManager.js +49 -10
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +76 -0
- package/dist/services/task/TaskUtils.js.map +1 -0
- package/dist/services/task/constants.js +20 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/index.js +46 -48
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/services/task/TaskUtils.d.ts +28 -0
- package/dist/types/services/task/constants.d.ts +17 -0
- package/dist/types/services/task/index.d.ts +21 -0
- package/dist/types/services/task/types.d.ts +96 -18
- package/dist/webex.js +1 -1
- package/package.json +3 -3
- package/src/cc.ts +1 -1
- package/src/services/task/TaskManager.ts +49 -7
- package/src/services/task/TaskUtils.ts +81 -0
- package/src/services/task/constants.ts +19 -0
- package/src/services/task/index.ts +20 -8
- package/src/services/task/types.ts +104 -18
- package/test/unit/spec/services/task/TaskManager.ts +256 -11
- package/test/unit/spec/services/task/TaskUtils.ts +131 -0
- package/test/unit/spec/services/task/index.ts +62 -11
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
import {ITask} from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines if the given agent is the primary agent (owner) of the task
|
|
6
|
+
* @param task - The task to check
|
|
7
|
+
* @param agentId - The agent ID to check for primary status
|
|
8
|
+
* @returns true if the agent is the primary agent, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export const isPrimary = (task: ITask, agentId: string): boolean => {
|
|
11
|
+
if (!task.data?.interaction?.owner) {
|
|
12
|
+
// Fall back to checking data.agentId when owner is not set
|
|
13
|
+
return task.data.agentId === agentId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return task.data.interaction.owner === agentId;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the given agent is a participant in the main interaction (mainCall)
|
|
21
|
+
* @param task - The task to check
|
|
22
|
+
* @param agentId - The agent ID to check for participation
|
|
23
|
+
* @returns true if the agent is a participant in the main interaction, false otherwise
|
|
24
|
+
*/
|
|
25
|
+
export const isParticipantInMainInteraction = (task: ITask, agentId: string): boolean => {
|
|
26
|
+
if (!task?.data?.interaction?.media) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Object.values(task.data.interaction.media).some(
|
|
31
|
+
(mediaObj) =>
|
|
32
|
+
mediaObj && mediaObj.mType === 'mainCall' && mediaObj.participants?.includes(agentId)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if the given agent is not in the interaction or has left the interaction
|
|
38
|
+
* @param task - The task to check
|
|
39
|
+
* @param agentId - The agent ID to check
|
|
40
|
+
* @returns true if the agent is not in the interaction or has left, false otherwise
|
|
41
|
+
*/
|
|
42
|
+
export const checkParticipantNotInInteraction = (task: ITask, agentId: string): boolean => {
|
|
43
|
+
if (!task?.data?.interaction?.participants) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const {data} = task;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
!(agentId in data.interaction.participants) ||
|
|
50
|
+
(agentId in data.interaction.participants && data.interaction.participants[agentId].hasLeft)
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Determines if a conference is currently in progress based on the number of active agent participants
|
|
56
|
+
* @param task - The task to check for conference status
|
|
57
|
+
* @returns true if there are 2 or more active agent participants in the main call, false otherwise
|
|
58
|
+
*/
|
|
59
|
+
export const getIsConferenceInProgress = (task: ITask): boolean => {
|
|
60
|
+
const mediaMainCall = task?.data?.interaction?.media?.[task?.data?.interactionId];
|
|
61
|
+
const participantsInMainCall = new Set(mediaMainCall?.participants);
|
|
62
|
+
const participants = task?.data?.interaction?.participants;
|
|
63
|
+
|
|
64
|
+
const agentParticipants = new Set();
|
|
65
|
+
if (participantsInMainCall.size > 0) {
|
|
66
|
+
participantsInMainCall.forEach((participantId: string) => {
|
|
67
|
+
const participant = participants?.[participantId];
|
|
68
|
+
if (
|
|
69
|
+
participant &&
|
|
70
|
+
participant.pType !== 'Customer' &&
|
|
71
|
+
participant.pType !== 'Supervisor' &&
|
|
72
|
+
!participant.hasLeft &&
|
|
73
|
+
participant.pType !== 'VVA'
|
|
74
|
+
) {
|
|
75
|
+
agentParticipants.add(participantId);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return agentParticipants.size >= 2;
|
|
81
|
+
};
|
|
@@ -23,6 +23,25 @@ export const CONFERENCE_TRANSFER = '/conference/transfer';
|
|
|
23
23
|
export const TASK_MANAGER_FILE = 'taskManager';
|
|
24
24
|
export const TASK_FILE = 'task';
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Task data field names that should be preserved during reconciliation
|
|
28
|
+
* These fields are retained even if not present in new data during updates
|
|
29
|
+
*/
|
|
30
|
+
export const PRESERVED_TASK_DATA_FIELDS = {
|
|
31
|
+
/** Indicates if the task is in consultation state */
|
|
32
|
+
IS_CONSULTED: 'isConsulted',
|
|
33
|
+
/** Indicates if wrap-up is required for this task */
|
|
34
|
+
WRAP_UP_REQUIRED: 'wrapUpRequired',
|
|
35
|
+
/** Indicates if a conference is currently in progress (2+ active agents) */
|
|
36
|
+
IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Array of task data field names that should not be deleted during reconciliation
|
|
41
|
+
* Used by reconcileData method to preserve important task state fields
|
|
42
|
+
*/
|
|
43
|
+
export const KEYS_TO_NOT_DELETE: string[] = Object.values(PRESERVED_TASK_DATA_FIELDS);
|
|
44
|
+
|
|
26
45
|
// METHOD NAMES
|
|
27
46
|
export const METHODS = {
|
|
28
47
|
// Task class methods
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {Failure} from '../core/GlobalTypes';
|
|
11
11
|
import {LoginOption} from '../../types';
|
|
12
12
|
import {TASK_FILE} from '../../constants';
|
|
13
|
-
import {METHODS} from './constants';
|
|
13
|
+
import {METHODS, KEYS_TO_NOT_DELETE} from './constants';
|
|
14
14
|
import routingContact from './contact';
|
|
15
15
|
import LoggerProxy from '../../logger-proxy';
|
|
16
16
|
import {
|
|
@@ -281,9 +281,24 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
281
281
|
* @private
|
|
282
282
|
*/
|
|
283
283
|
private reconcileData(oldData: TaskData, newData: TaskData): TaskData {
|
|
284
|
+
// Remove keys from oldData that are not in newData
|
|
285
|
+
Object.keys(oldData).forEach((key) => {
|
|
286
|
+
if (!(key in newData) && !KEYS_TO_NOT_DELETE.includes(key as string)) {
|
|
287
|
+
delete oldData[key];
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Merge or update keys from newData
|
|
284
292
|
Object.keys(newData).forEach((key) => {
|
|
285
|
-
if (
|
|
286
|
-
|
|
293
|
+
if (
|
|
294
|
+
newData[key] &&
|
|
295
|
+
typeof newData[key] === 'object' &&
|
|
296
|
+
!Array.isArray(newData[key]) &&
|
|
297
|
+
oldData[key] &&
|
|
298
|
+
typeof oldData[key] === 'object' &&
|
|
299
|
+
!Array.isArray(oldData[key])
|
|
300
|
+
) {
|
|
301
|
+
this.reconcileData(oldData[key], newData[key]);
|
|
287
302
|
} else {
|
|
288
303
|
oldData[key] = newData[key];
|
|
289
304
|
}
|
|
@@ -1684,9 +1699,6 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1684
1699
|
}
|
|
1685
1700
|
}
|
|
1686
1701
|
|
|
1687
|
-
// TODO: Uncomment this method in future PR for Multi-Party Conference support (>3 participants)
|
|
1688
|
-
// Conference transfer will be supported when implementing enhanced multi-party conference functionality
|
|
1689
|
-
/*
|
|
1690
1702
|
/**
|
|
1691
1703
|
* Transfers the current conference to another agent
|
|
1692
1704
|
*
|
|
@@ -1707,7 +1719,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1707
1719
|
* }
|
|
1708
1720
|
* ```
|
|
1709
1721
|
*/
|
|
1710
|
-
|
|
1722
|
+
public async transferConference(): Promise<TaskResponse> {
|
|
1711
1723
|
try {
|
|
1712
1724
|
LoggerProxy.info(`Transferring conference`, {
|
|
1713
1725
|
module: TASK_FILE,
|
|
@@ -1771,5 +1783,5 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1771
1783
|
|
|
1772
1784
|
throw err;
|
|
1773
1785
|
}
|
|
1774
|
-
}
|
|
1786
|
+
}
|
|
1775
1787
|
}
|
|
@@ -733,6 +733,8 @@ export type TaskData = {
|
|
|
733
733
|
isConsulted?: boolean;
|
|
734
734
|
/** Indicates if the task is in conference state */
|
|
735
735
|
isConferencing: boolean;
|
|
736
|
+
/** Indicates if a conference is currently in progress (2+ active agents) */
|
|
737
|
+
isConferenceInProgress?: boolean;
|
|
736
738
|
/** Identifier of agent who last updated the task */
|
|
737
739
|
updatedBy?: string;
|
|
738
740
|
/** Type of destination for transfer/consult */
|
|
@@ -1135,16 +1137,16 @@ export interface ITask extends EventEmitter {
|
|
|
1135
1137
|
autoWrapup?: AutoWrapup;
|
|
1136
1138
|
|
|
1137
1139
|
/**
|
|
1138
|
-
*
|
|
1139
|
-
* This method stops the auto-wrapup process if it is currently active
|
|
1140
|
+
* Cancels the auto-wrapup timer for the task.
|
|
1141
|
+
* This method stops the auto-wrapup process if it is currently active.
|
|
1140
1142
|
* Note: This is supported only in single session mode. Not supported in multi-session mode.
|
|
1141
1143
|
* @returns void
|
|
1142
1144
|
*/
|
|
1143
1145
|
cancelAutoWrapupTimer(): void;
|
|
1144
1146
|
|
|
1145
1147
|
/**
|
|
1146
|
-
* Deregisters all web call event listeners
|
|
1147
|
-
* Used when cleaning up task resources
|
|
1148
|
+
* Deregisters all web call event listeners.
|
|
1149
|
+
* Used when cleaning up task resources.
|
|
1148
1150
|
* @ignore
|
|
1149
1151
|
*/
|
|
1150
1152
|
unregisterWebCallListeners(): void;
|
|
@@ -1164,7 +1166,7 @@ export interface ITask extends EventEmitter {
|
|
|
1164
1166
|
* @returns Promise<TaskResponse>
|
|
1165
1167
|
* @example
|
|
1166
1168
|
* ```typescript
|
|
1167
|
-
* task.accept();
|
|
1169
|
+
* await task.accept();
|
|
1168
1170
|
* ```
|
|
1169
1171
|
*/
|
|
1170
1172
|
accept(): Promise<TaskResponse>;
|
|
@@ -1174,48 +1176,48 @@ export interface ITask extends EventEmitter {
|
|
|
1174
1176
|
* @returns Promise<TaskResponse>
|
|
1175
1177
|
* @example
|
|
1176
1178
|
* ```typescript
|
|
1177
|
-
* task.decline();
|
|
1179
|
+
* await task.decline();
|
|
1178
1180
|
* ```
|
|
1179
1181
|
*/
|
|
1180
1182
|
decline(): Promise<TaskResponse>;
|
|
1181
1183
|
|
|
1182
1184
|
/**
|
|
1183
|
-
* Places the current task on hold
|
|
1185
|
+
* Places the current task on hold.
|
|
1184
1186
|
* @returns Promise<TaskResponse>
|
|
1185
1187
|
* @example
|
|
1186
1188
|
* ```typescript
|
|
1187
|
-
* task.hold();
|
|
1189
|
+
* await task.hold();
|
|
1188
1190
|
* ```
|
|
1189
1191
|
*/
|
|
1190
1192
|
hold(): Promise<TaskResponse>;
|
|
1191
1193
|
|
|
1192
1194
|
/**
|
|
1193
|
-
* Resumes a task that was previously on hold
|
|
1195
|
+
* Resumes a task that was previously on hold.
|
|
1194
1196
|
* @returns Promise<TaskResponse>
|
|
1195
1197
|
* @example
|
|
1196
1198
|
* ```typescript
|
|
1197
|
-
* task.resume();
|
|
1199
|
+
* await task.resume();
|
|
1198
1200
|
* ```
|
|
1199
1201
|
*/
|
|
1200
1202
|
resume(): Promise<TaskResponse>;
|
|
1201
1203
|
|
|
1202
1204
|
/**
|
|
1203
|
-
* Ends/terminates the current task
|
|
1205
|
+
* Ends/terminates the current task.
|
|
1204
1206
|
* @returns Promise<TaskResponse>
|
|
1205
1207
|
* @example
|
|
1206
1208
|
* ```typescript
|
|
1207
|
-
* task.end();
|
|
1209
|
+
* await task.end();
|
|
1208
1210
|
* ```
|
|
1209
1211
|
*/
|
|
1210
1212
|
end(): Promise<TaskResponse>;
|
|
1211
1213
|
|
|
1212
1214
|
/**
|
|
1213
|
-
* Initiates wrap-up process for the task with specified details
|
|
1215
|
+
* Initiates wrap-up process for the task with specified details.
|
|
1214
1216
|
* @param wrapupPayload - Wrap-up details including reason and auxiliary code
|
|
1215
1217
|
* @returns Promise<TaskResponse>
|
|
1216
1218
|
* @example
|
|
1217
1219
|
* ```typescript
|
|
1218
|
-
* task.wrapup({
|
|
1220
|
+
* await task.wrapup({
|
|
1219
1221
|
* wrapUpReason: "Customer issue resolved",
|
|
1220
1222
|
* auxCodeId: "RESOLVED"
|
|
1221
1223
|
* });
|
|
@@ -1224,25 +1226,109 @@ export interface ITask extends EventEmitter {
|
|
|
1224
1226
|
wrapup(wrapupPayload: WrapupPayLoad): Promise<TaskResponse>;
|
|
1225
1227
|
|
|
1226
1228
|
/**
|
|
1227
|
-
* Pauses the recording for current task
|
|
1229
|
+
* Pauses the recording for current task.
|
|
1228
1230
|
* @returns Promise<TaskResponse>
|
|
1229
1231
|
* @example
|
|
1230
1232
|
* ```typescript
|
|
1231
|
-
* task.pauseRecording();
|
|
1233
|
+
* await task.pauseRecording();
|
|
1232
1234
|
* ```
|
|
1233
1235
|
*/
|
|
1234
1236
|
pauseRecording(): Promise<TaskResponse>;
|
|
1235
1237
|
|
|
1236
1238
|
/**
|
|
1237
|
-
* Resumes a previously paused recording
|
|
1239
|
+
* Resumes a previously paused recording.
|
|
1238
1240
|
* @param resumeRecordingPayload - Parameters for resuming the recording
|
|
1239
1241
|
* @returns Promise<TaskResponse>
|
|
1240
1242
|
* @example
|
|
1241
1243
|
* ```typescript
|
|
1242
|
-
* task.resumeRecording({
|
|
1244
|
+
* await task.resumeRecording({
|
|
1243
1245
|
* autoResumed: false
|
|
1244
1246
|
* });
|
|
1245
1247
|
* ```
|
|
1246
1248
|
*/
|
|
1247
1249
|
resumeRecording(resumeRecordingPayload: ResumeRecordingPayload): Promise<TaskResponse>;
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Initiates a consultation with another agent or queue.
|
|
1253
|
+
* @param consultPayload - Consultation details including destination and type
|
|
1254
|
+
* @returns Promise<TaskResponse>
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```typescript
|
|
1257
|
+
* await task.consult({ to: "agentId", destinationType: "agent" });
|
|
1258
|
+
* ```
|
|
1259
|
+
*/
|
|
1260
|
+
consult(consultPayload: ConsultPayload): Promise<TaskResponse>;
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Ends an ongoing consultation.
|
|
1264
|
+
* @param consultEndPayload - Details for ending the consultation
|
|
1265
|
+
* @returns Promise<TaskResponse>
|
|
1266
|
+
* @example
|
|
1267
|
+
* ```typescript
|
|
1268
|
+
* await task.endConsult({ isConsult: true, taskId: "taskId" });
|
|
1269
|
+
* ```
|
|
1270
|
+
*/
|
|
1271
|
+
endConsult(consultEndPayload: ConsultEndPayload): Promise<TaskResponse>;
|
|
1272
|
+
|
|
1273
|
+
/**
|
|
1274
|
+
* Transfers the task to another agent or queue.
|
|
1275
|
+
* @param transferPayload - Transfer details including destination and type
|
|
1276
|
+
* @returns Promise<TaskResponse>
|
|
1277
|
+
* @example
|
|
1278
|
+
* ```typescript
|
|
1279
|
+
* await task.transfer({ to: "queueId", destinationType: "queue" });
|
|
1280
|
+
* ```
|
|
1281
|
+
*/
|
|
1282
|
+
transfer(transferPayload: TransferPayLoad): Promise<TaskResponse>;
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Transfers the task after consultation.
|
|
1286
|
+
* @param consultTransferPayload - Details for consult transfer (optional)
|
|
1287
|
+
* @returns Promise<TaskResponse>
|
|
1288
|
+
* @example
|
|
1289
|
+
* ```typescript
|
|
1290
|
+
* await task.consultTransfer({ to: "agentId", destinationType: "agent" });
|
|
1291
|
+
* ```
|
|
1292
|
+
*/
|
|
1293
|
+
consultTransfer(consultTransferPayload?: ConsultTransferPayLoad): Promise<TaskResponse>;
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Initiates a consult conference (merge consult call with main call).
|
|
1297
|
+
* @returns Promise<TaskResponse>
|
|
1298
|
+
* @example
|
|
1299
|
+
* ```typescript
|
|
1300
|
+
* await task.consultConference();
|
|
1301
|
+
* ```
|
|
1302
|
+
*/
|
|
1303
|
+
consultConference(): Promise<TaskResponse>;
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Exits from an ongoing conference.
|
|
1307
|
+
* @returns Promise<TaskResponse>
|
|
1308
|
+
* @example
|
|
1309
|
+
* ```typescript
|
|
1310
|
+
* await task.exitConference();
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
exitConference(): Promise<TaskResponse>;
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Transfers the conference to another participant.
|
|
1317
|
+
* @returns Promise<TaskResponse>
|
|
1318
|
+
* @example
|
|
1319
|
+
* ```typescript
|
|
1320
|
+
* await task.transferConference();
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
transferConference(): Promise<TaskResponse>;
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Toggles mute/unmute for the local audio stream during a WebRTC task.
|
|
1327
|
+
* @returns Promise<void>
|
|
1328
|
+
* @example
|
|
1329
|
+
* ```typescript
|
|
1330
|
+
* await task.toggleMute();
|
|
1331
|
+
* ```
|
|
1332
|
+
*/
|
|
1333
|
+
toggleMute(): Promise<void>;
|
|
1248
1334
|
}
|
|
@@ -1357,8 +1357,12 @@ describe('TaskManager', () => {
|
|
|
1357
1357
|
|
|
1358
1358
|
describe('Conference event handling', () => {
|
|
1359
1359
|
let task;
|
|
1360
|
+
const agentId = '723a8ffb-a26e-496d-b14a-ff44fb83b64f';
|
|
1360
1361
|
|
|
1361
1362
|
beforeEach(() => {
|
|
1363
|
+
// Set the agentId on taskManager before tests run
|
|
1364
|
+
taskManager.setAgentId(agentId);
|
|
1365
|
+
|
|
1362
1366
|
task = {
|
|
1363
1367
|
data: { interactionId: taskId },
|
|
1364
1368
|
emit: jest.fn(),
|
|
@@ -1434,19 +1438,260 @@ describe('TaskManager', () => {
|
|
|
1434
1438
|
// No specific task event emission for participant joined - just data update
|
|
1435
1439
|
});
|
|
1436
1440
|
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1441
|
+
describe('PARTICIPANT_LEFT_CONFERENCE event handling', () => {
|
|
1442
|
+
it('should emit TASK_PARTICIPANT_LEFT event when participant leaves conference', () => {
|
|
1443
|
+
const payload = {
|
|
1444
|
+
data: {
|
|
1445
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1446
|
+
interactionId: taskId,
|
|
1447
|
+
interaction: {
|
|
1448
|
+
participants: {
|
|
1449
|
+
[agentId]: {
|
|
1450
|
+
hasLeft: false,
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
},
|
|
1455
|
+
};
|
|
1445
1456
|
|
|
1446
|
-
|
|
1457
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1458
|
+
|
|
1459
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
it('should NOT remove task when agent is still in interaction', () => {
|
|
1463
|
+
const payload = {
|
|
1464
|
+
data: {
|
|
1465
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1466
|
+
interactionId: taskId,
|
|
1467
|
+
interaction: {
|
|
1468
|
+
participants: {
|
|
1469
|
+
[agentId]: {
|
|
1470
|
+
hasLeft: false,
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
},
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1447
1478
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1479
|
+
// Task should still exist in collection
|
|
1480
|
+
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
1481
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
it('should NOT remove task when agent left but is in main interaction', () => {
|
|
1485
|
+
const payload = {
|
|
1486
|
+
data: {
|
|
1487
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1488
|
+
interactionId: taskId,
|
|
1489
|
+
interaction: {
|
|
1490
|
+
participants: {
|
|
1491
|
+
[agentId]: {
|
|
1492
|
+
hasLeft: true,
|
|
1493
|
+
},
|
|
1494
|
+
},
|
|
1495
|
+
media: {
|
|
1496
|
+
[taskId]: {
|
|
1497
|
+
mType: 'mainCall',
|
|
1498
|
+
participants: [agentId],
|
|
1499
|
+
},
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
},
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1506
|
+
|
|
1507
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1508
|
+
|
|
1509
|
+
// Task should still exist - not removed
|
|
1510
|
+
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
1511
|
+
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
1512
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it('should NOT remove task when agent left but is primary (owner)', () => {
|
|
1516
|
+
const payload = {
|
|
1517
|
+
data: {
|
|
1518
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1519
|
+
interactionId: taskId,
|
|
1520
|
+
interaction: {
|
|
1521
|
+
participants: {
|
|
1522
|
+
[agentId]: {
|
|
1523
|
+
hasLeft: true,
|
|
1524
|
+
},
|
|
1525
|
+
},
|
|
1526
|
+
owner: agentId,
|
|
1527
|
+
media: {
|
|
1528
|
+
[taskId]: {
|
|
1529
|
+
mType: 'consultCall',
|
|
1530
|
+
participants: ['other-agent'],
|
|
1531
|
+
},
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
};
|
|
1536
|
+
|
|
1537
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1538
|
+
|
|
1539
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1540
|
+
|
|
1541
|
+
// Task should still exist - not removed because agent is primary
|
|
1542
|
+
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
1543
|
+
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
1544
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1545
|
+
});
|
|
1546
|
+
|
|
1547
|
+
it('should remove task when agent left and is NOT in main interaction and is NOT primary', () => {
|
|
1548
|
+
const payload = {
|
|
1549
|
+
data: {
|
|
1550
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1551
|
+
interactionId: taskId,
|
|
1552
|
+
interaction: {
|
|
1553
|
+
participants: {
|
|
1554
|
+
[agentId]: {
|
|
1555
|
+
hasLeft: true,
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
owner: 'another-agent-id',
|
|
1559
|
+
media: {
|
|
1560
|
+
[taskId]: {
|
|
1561
|
+
mType: 'mainCall',
|
|
1562
|
+
participants: ['another-agent-id'],
|
|
1563
|
+
},
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1570
|
+
|
|
1571
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1572
|
+
|
|
1573
|
+
// Task should be removed
|
|
1574
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1575
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1576
|
+
});
|
|
1577
|
+
|
|
1578
|
+
it('should remove task when agent is not in participants list', () => {
|
|
1579
|
+
const payload = {
|
|
1580
|
+
data: {
|
|
1581
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1582
|
+
interactionId: taskId,
|
|
1583
|
+
interaction: {
|
|
1584
|
+
participants: {
|
|
1585
|
+
'other-agent-id': {
|
|
1586
|
+
hasLeft: false,
|
|
1587
|
+
},
|
|
1588
|
+
},
|
|
1589
|
+
owner: 'another-agent-id',
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1595
|
+
|
|
1596
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1597
|
+
|
|
1598
|
+
// Task should be removed because agent is not in participants
|
|
1599
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1600
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
it('should update isConferenceInProgress based on remaining active agents', () => {
|
|
1604
|
+
const payload = {
|
|
1605
|
+
data: {
|
|
1606
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1607
|
+
interactionId: taskId,
|
|
1608
|
+
interaction: {
|
|
1609
|
+
participants: {
|
|
1610
|
+
[agentId]: {
|
|
1611
|
+
hasLeft: false,
|
|
1612
|
+
pType: 'Agent',
|
|
1613
|
+
},
|
|
1614
|
+
'agent-2': {
|
|
1615
|
+
hasLeft: false,
|
|
1616
|
+
pType: 'Agent',
|
|
1617
|
+
},
|
|
1618
|
+
'customer-1': {
|
|
1619
|
+
hasLeft: false,
|
|
1620
|
+
pType: 'Customer',
|
|
1621
|
+
},
|
|
1622
|
+
},
|
|
1623
|
+
media: {
|
|
1624
|
+
[taskId]: {
|
|
1625
|
+
mType: 'mainCall',
|
|
1626
|
+
participants: [agentId, 'agent-2', 'customer-1'],
|
|
1627
|
+
},
|
|
1628
|
+
},
|
|
1629
|
+
},
|
|
1630
|
+
},
|
|
1631
|
+
};
|
|
1632
|
+
|
|
1633
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1634
|
+
|
|
1635
|
+
// isConferenceInProgress should be true (2 active agents)
|
|
1636
|
+
expect(task.data.isConferenceInProgress).toBe(true);
|
|
1637
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
it('should set isConferenceInProgress to false when only one agent remains', () => {
|
|
1641
|
+
const payload = {
|
|
1642
|
+
data: {
|
|
1643
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1644
|
+
interactionId: taskId,
|
|
1645
|
+
interaction: {
|
|
1646
|
+
participants: {
|
|
1647
|
+
[agentId]: {
|
|
1648
|
+
hasLeft: false,
|
|
1649
|
+
pType: 'Agent',
|
|
1650
|
+
},
|
|
1651
|
+
'agent-2': {
|
|
1652
|
+
hasLeft: true,
|
|
1653
|
+
pType: 'Agent',
|
|
1654
|
+
},
|
|
1655
|
+
'customer-1': {
|
|
1656
|
+
hasLeft: false,
|
|
1657
|
+
pType: 'Customer',
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
media: {
|
|
1661
|
+
[taskId]: {
|
|
1662
|
+
mType: 'mainCall',
|
|
1663
|
+
participants: [agentId, 'customer-1'],
|
|
1664
|
+
},
|
|
1665
|
+
},
|
|
1666
|
+
},
|
|
1667
|
+
},
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1671
|
+
|
|
1672
|
+
// isConferenceInProgress should be false (only 1 active agent)
|
|
1673
|
+
expect(task.data.isConferenceInProgress).toBe(false);
|
|
1674
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
it('should handle participant left when no participants data exists', () => {
|
|
1678
|
+
const payload = {
|
|
1679
|
+
data: {
|
|
1680
|
+
type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
1681
|
+
interactionId: taskId,
|
|
1682
|
+
interaction: {},
|
|
1683
|
+
},
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
1687
|
+
|
|
1688
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
1689
|
+
|
|
1690
|
+
// When no participants data exists, checkParticipantNotInInteraction returns true
|
|
1691
|
+
// Since agent won't be in main interaction either, task should be removed
|
|
1692
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
1693
|
+
expect(task.emit).toHaveBeenCalledWith(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
1694
|
+
});
|
|
1450
1695
|
});
|
|
1451
1696
|
|
|
1452
1697
|
it('should handle PARTICIPANT_LEFT_CONFERENCE_FAILED event', () => {
|