@webex/contact-center 3.10.0-next.18 → 3.10.0-next.19
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 -0
- package/dist/cc.js.map +1 -1
- package/dist/metrics/behavioral-events.js +12 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +4 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/task/TaskManager.js +112 -33
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +90 -1
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +3 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/services/task/TaskUtils.d.ts +42 -0
- package/dist/types/services/task/constants.d.ts +2 -0
- package/dist/types/services/task/types.d.ts +6 -0
- package/dist/webex.js +1 -1
- package/package.json +1 -1
- package/src/cc.ts +1 -0
- package/src/metrics/behavioral-events.ts +12 -0
- package/src/metrics/constants.ts +4 -0
- package/src/services/task/TaskManager.ts +127 -29
- package/src/services/task/TaskUtils.ts +109 -1
- package/src/services/task/constants.ts +2 -0
- package/src/services/task/types.ts +6 -0
- package/test/unit/spec/cc.ts +1 -0
- package/test/unit/spec/metrics/behavioral-events.ts +14 -0
- package/test/unit/spec/services/task/TaskManager.ts +143 -0
- package/test/unit/spec/services/task/TaskUtils.ts +305 -3
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isParticipantInMainInteraction,
|
|
19
19
|
isPrimary,
|
|
20
20
|
isSecondaryEpDnAgent,
|
|
21
|
+
shouldAutoAnswerTask,
|
|
21
22
|
} from './TaskUtils';
|
|
22
23
|
|
|
23
24
|
/** @internal */
|
|
@@ -36,6 +37,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
36
37
|
private static taskManager;
|
|
37
38
|
private wrapupData: WrapupData;
|
|
38
39
|
private agentId: string;
|
|
40
|
+
private webRtcEnabled: boolean;
|
|
39
41
|
/**
|
|
40
42
|
* @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
|
|
41
43
|
* @param webCallingService - Webrtc Service Layer
|
|
@@ -73,6 +75,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
73
75
|
return this.agentId;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
public setWebRtcEnabled(webRtcEnabled: boolean) {
|
|
79
|
+
this.webRtcEnabled = webRtcEnabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
76
82
|
private handleIncomingWebCall = (call: ICall) => {
|
|
77
83
|
const currentTask = Object.values(this.taskCollection).find(
|
|
78
84
|
(task) => task.data.interaction.mediaType === 'telephony'
|
|
@@ -130,6 +136,14 @@ export default class TaskManager extends EventEmitter {
|
|
|
130
136
|
interactionId: payload.data.interactionId,
|
|
131
137
|
});
|
|
132
138
|
|
|
139
|
+
// Check if auto-answer should happen for this task
|
|
140
|
+
const shouldAutoAnswer = shouldAutoAnswerTask(
|
|
141
|
+
payload.data,
|
|
142
|
+
this.agentId,
|
|
143
|
+
this.webCallingService.loginOption,
|
|
144
|
+
this.webRtcEnabled
|
|
145
|
+
);
|
|
146
|
+
|
|
133
147
|
task = new Task(
|
|
134
148
|
this.contact,
|
|
135
149
|
this.webCallingService,
|
|
@@ -138,6 +152,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
138
152
|
wrapUpRequired:
|
|
139
153
|
payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
140
154
|
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
155
|
+
isAutoAnswering: shouldAutoAnswer, // Set flag before emitting
|
|
141
156
|
},
|
|
142
157
|
this.wrapupData,
|
|
143
158
|
this.agentId
|
|
@@ -169,13 +184,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
169
184
|
}
|
|
170
185
|
break;
|
|
171
186
|
|
|
172
|
-
case CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
187
|
+
case CC_EVENTS.AGENT_CONTACT_RESERVED: {
|
|
188
|
+
// Check if auto-answer should happen for this task
|
|
189
|
+
const shouldAutoAnswerReserved = shouldAutoAnswerTask(
|
|
190
|
+
payload.data,
|
|
191
|
+
this.agentId,
|
|
192
|
+
this.webCallingService.loginOption,
|
|
193
|
+
this.webRtcEnabled
|
|
194
|
+
);
|
|
195
|
+
|
|
173
196
|
task = new Task(
|
|
174
197
|
this.contact,
|
|
175
198
|
this.webCallingService,
|
|
176
199
|
{
|
|
177
200
|
...payload.data,
|
|
178
201
|
isConsulted: false,
|
|
202
|
+
isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting
|
|
179
203
|
},
|
|
180
204
|
this.wrapupData,
|
|
181
205
|
this.agentId
|
|
@@ -190,6 +214,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
190
214
|
this.emit(TASK_EVENTS.TASK_INCOMING, task);
|
|
191
215
|
}
|
|
192
216
|
break;
|
|
217
|
+
}
|
|
193
218
|
case CC_EVENTS.AGENT_OFFER_CONTACT:
|
|
194
219
|
// We don't have to emit any event here since this will be result of promise.
|
|
195
220
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -199,24 +224,29 @@ export default class TaskManager extends EventEmitter {
|
|
|
199
224
|
interactionId: payload.data?.interactionId,
|
|
200
225
|
});
|
|
201
226
|
this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task);
|
|
227
|
+
|
|
228
|
+
// Handle auto-answer for offer contact
|
|
229
|
+
this.handleAutoAnswer(task);
|
|
202
230
|
break;
|
|
203
231
|
case CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
if (task) {
|
|
233
|
+
task = this.updateTaskData(task, payload.data);
|
|
234
|
+
this.metricsManager.trackEvent(
|
|
235
|
+
METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED,
|
|
236
|
+
{
|
|
237
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(payload.data),
|
|
238
|
+
taskId: payload.data.interactionId,
|
|
239
|
+
reason: payload.data.reasonCode || payload.data.reason,
|
|
240
|
+
},
|
|
241
|
+
['behavioral', 'operational']
|
|
242
|
+
);
|
|
243
|
+
LoggerProxy.log(`Agent outbound failed for task`, {
|
|
244
|
+
module: TASK_MANAGER_FILE,
|
|
245
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
246
|
+
interactionId: payload.data.interactionId,
|
|
247
|
+
});
|
|
248
|
+
task.emit(TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
|
|
249
|
+
}
|
|
220
250
|
break;
|
|
221
251
|
case CC_EVENTS.AGENT_CONTACT_ASSIGNED:
|
|
222
252
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -256,18 +286,19 @@ export default class TaskManager extends EventEmitter {
|
|
|
256
286
|
}
|
|
257
287
|
case CC_EVENTS.CONTACT_ENDED:
|
|
258
288
|
// Update task data
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
// Handle cleanup based on whether task should be deleted
|
|
267
|
-
this.handleTaskCleanup(task);
|
|
289
|
+
if (task) {
|
|
290
|
+
task = this.updateTaskData(task, {
|
|
291
|
+
...payload.data,
|
|
292
|
+
wrapUpRequired:
|
|
293
|
+
payload.data.interaction.state !== 'new' &&
|
|
294
|
+
!isSecondaryEpDnAgent(payload.data.interaction),
|
|
295
|
+
});
|
|
268
296
|
|
|
269
|
-
|
|
297
|
+
// Handle cleanup based on whether task should be deleted
|
|
298
|
+
this.handleTaskCleanup(task);
|
|
270
299
|
|
|
300
|
+
task?.emit(TASK_EVENTS.TASK_END, task);
|
|
301
|
+
}
|
|
271
302
|
break;
|
|
272
303
|
case CC_EVENTS.CONTACT_MERGED:
|
|
273
304
|
task = this.handleContactMerged(task, payload.data);
|
|
@@ -308,6 +339,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
308
339
|
isConsulted: true, // This ensures that the task is marked as us being requested for a consult
|
|
309
340
|
});
|
|
310
341
|
task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task);
|
|
342
|
+
|
|
343
|
+
// Handle auto-answer for consult offer
|
|
344
|
+
this.handleAutoAnswer(task);
|
|
311
345
|
break;
|
|
312
346
|
case CC_EVENTS.AGENT_CONSULTING:
|
|
313
347
|
// Received when agent is in an active consult state
|
|
@@ -545,6 +579,70 @@ export default class TaskManager extends EventEmitter {
|
|
|
545
579
|
}
|
|
546
580
|
}
|
|
547
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Handles auto-answer logic for incoming tasks
|
|
584
|
+
* Automatically accepts tasks when isAutoAnswering flag is set
|
|
585
|
+
* The flag is set during task creation based on:
|
|
586
|
+
* 1. WebRTC calls with auto-answer enabled in agent profile
|
|
587
|
+
* 2. Agent-initiated WebRTC outdial calls
|
|
588
|
+
* 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
|
|
589
|
+
*
|
|
590
|
+
* @param task - The task to auto-answer
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
private async handleAutoAnswer(task: ITask): Promise<void> {
|
|
594
|
+
if (!task || !task.data || !task.data.isAutoAnswering) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
LoggerProxy.info(`Auto-answering task`, {
|
|
599
|
+
module: TASK_MANAGER_FILE,
|
|
600
|
+
method: 'handleAutoAnswer',
|
|
601
|
+
interactionId: task.data.interactionId,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
await task.accept();
|
|
606
|
+
LoggerProxy.info(`Task auto-answered successfully`, {
|
|
607
|
+
module: TASK_MANAGER_FILE,
|
|
608
|
+
method: 'handleAutoAnswer',
|
|
609
|
+
interactionId: task.data.interactionId,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Track successful auto-answer
|
|
613
|
+
this.metricsManager.trackEvent(
|
|
614
|
+
METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS,
|
|
615
|
+
{
|
|
616
|
+
taskId: task.data.interactionId,
|
|
617
|
+
mediaType: task.data.interaction.mediaType,
|
|
618
|
+
isAutoAnswered: true,
|
|
619
|
+
},
|
|
620
|
+
['behavioral', 'operational']
|
|
621
|
+
);
|
|
622
|
+
} catch (error) {
|
|
623
|
+
// Reset isAutoAnswering flag on failure
|
|
624
|
+
task.updateTaskData({...task.data, isAutoAnswering: false});
|
|
625
|
+
LoggerProxy.error(`Failed to auto-answer task`, {
|
|
626
|
+
module: TASK_MANAGER_FILE,
|
|
627
|
+
method: 'handleAutoAnswer',
|
|
628
|
+
interactionId: task.data.interactionId,
|
|
629
|
+
error,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Track auto-answer failure
|
|
633
|
+
this.metricsManager.trackEvent(
|
|
634
|
+
METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED,
|
|
635
|
+
{
|
|
636
|
+
taskId: task.data.interactionId,
|
|
637
|
+
mediaType: task.data.interaction.mediaType,
|
|
638
|
+
error: error?.message || 'Unknown error',
|
|
639
|
+
isAutoAnswered: false,
|
|
640
|
+
},
|
|
641
|
+
['behavioral', 'operational']
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
548
646
|
/**
|
|
549
647
|
* Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal
|
|
550
648
|
* @param task - The task to clean up
|
|
@@ -562,13 +660,13 @@ export default class TaskManager extends EventEmitter {
|
|
|
562
660
|
|
|
563
661
|
const isOutdial = task.data.interaction.outboundType === 'OUTDIAL';
|
|
564
662
|
const isNew = task.data.interaction.state === 'new';
|
|
565
|
-
const
|
|
663
|
+
const needsWrapUp = task.data.agentsPendingWrapUp?.length > 0;
|
|
566
664
|
|
|
567
665
|
// For OUTDIAL: only remove if NOT terminated (user-declined, no wrap-up follows)
|
|
568
666
|
// If terminated, keep task for wrap-up flow (CONTACT_ENDED → AGENT_WRAPUP)
|
|
569
667
|
// For non-OUTDIAL: remove if state is 'new'
|
|
570
668
|
// Always remove if secondary EpDn agent
|
|
571
|
-
if ((isNew && !(isOutdial &&
|
|
669
|
+
if ((isNew && !(isOutdial && needsWrapUp)) || isSecondaryEpDnAgent(task.data.interaction)) {
|
|
572
670
|
this.removeTaskFromCollection(task);
|
|
573
671
|
}
|
|
574
672
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {Interaction, ITask, TaskData} from './types';
|
|
2
|
+
import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
|
|
3
|
+
import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants';
|
|
4
|
+
import {LoginOption} from '../../types';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Determines if the given agent is the primary agent (owner) of the task
|
|
@@ -111,3 +113,109 @@ export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
|
|
|
111
113
|
|
|
112
114
|
return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
|
|
113
115
|
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks if auto-answer is enabled for the agent participant
|
|
119
|
+
* @param interaction - The interaction object
|
|
120
|
+
* @param agentId - Current agent ID
|
|
121
|
+
* @returns true if auto-answer is enabled, false otherwise
|
|
122
|
+
*/
|
|
123
|
+
export const isAutoAnswerEnabled = (interaction: Interaction, agentId: string): boolean => {
|
|
124
|
+
return interaction.participants?.[agentId]?.autoAnswerEnabled === true;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Checks if the interaction is a WebRTC call eligible for auto-answer
|
|
129
|
+
* @param interaction - The interaction object
|
|
130
|
+
* @param loginOption - The agent's login option (BROWSER, AGENT_DN, etc.)
|
|
131
|
+
* @param webRtcEnabled - Whether WebRTC is enabled for the agent
|
|
132
|
+
* @returns true if this is a WebRTC call, false otherwise
|
|
133
|
+
*/
|
|
134
|
+
export const isWebRTCCall = (
|
|
135
|
+
interaction: Interaction,
|
|
136
|
+
loginOption: string,
|
|
137
|
+
webRtcEnabled: boolean
|
|
138
|
+
): boolean => {
|
|
139
|
+
return (
|
|
140
|
+
webRtcEnabled &&
|
|
141
|
+
loginOption === LoginOption.BROWSER &&
|
|
142
|
+
interaction.mediaType === OUTDIAL_MEDIA_TYPE
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Checks if the interaction is a digital outbound (Email/SMS)
|
|
148
|
+
* @param interaction - The interaction object
|
|
149
|
+
* @returns true if this is a digital outbound, false otherwise
|
|
150
|
+
*/
|
|
151
|
+
export const isDigitalOutbound = (interaction: Interaction): boolean => {
|
|
152
|
+
return (
|
|
153
|
+
interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
|
|
154
|
+
interaction.outboundType === OUTBOUND_TYPE &&
|
|
155
|
+
(interaction.mediaChannel === MEDIA_CHANNEL.EMAIL ||
|
|
156
|
+
interaction.mediaChannel === MEDIA_CHANNEL.SMS)
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Checks if the outdial was initiated by the current agent
|
|
162
|
+
* @param interaction - The interaction object
|
|
163
|
+
* @param agentId - Current agent ID
|
|
164
|
+
* @returns true if agent initiated the outdial, false otherwise
|
|
165
|
+
*/
|
|
166
|
+
export const hasAgentInitiatedOutdial = (interaction: Interaction, agentId: string): boolean => {
|
|
167
|
+
return (
|
|
168
|
+
interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
|
|
169
|
+
interaction.outboundType === OUTBOUND_TYPE &&
|
|
170
|
+
interaction.callProcessingDetails?.outdialAgentId === agentId &&
|
|
171
|
+
interaction.owner === agentId &&
|
|
172
|
+
!interaction.callProcessingDetails?.BLIND_TRANSFER_IN_PROGRESS
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Determines if a task should be auto-answered based on interaction data
|
|
178
|
+
* Auto-answer logic handles:
|
|
179
|
+
* 1. WebRTC calls with auto-answer enabled in agent profile
|
|
180
|
+
* 2. Agent-initiated WebRTC outdial calls
|
|
181
|
+
* 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
|
|
182
|
+
*
|
|
183
|
+
* @param taskData - The task data
|
|
184
|
+
* @param agentId - Current agent ID
|
|
185
|
+
* @param loginOption - Agent's login option
|
|
186
|
+
* @param webRtcEnabled - Whether WebRTC is enabled for the agent
|
|
187
|
+
* @returns true if task should be auto-answered, false otherwise
|
|
188
|
+
*/
|
|
189
|
+
export const shouldAutoAnswerTask = (
|
|
190
|
+
taskData: TaskData,
|
|
191
|
+
agentId: string,
|
|
192
|
+
loginOption: string,
|
|
193
|
+
webRtcEnabled: boolean
|
|
194
|
+
): boolean => {
|
|
195
|
+
const {interaction} = taskData;
|
|
196
|
+
|
|
197
|
+
if (!interaction || !agentId) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if auto-answer is enabled for this agent
|
|
202
|
+
const autoAnswerEnabled = isAutoAnswerEnabled(interaction, agentId);
|
|
203
|
+
|
|
204
|
+
// Check if this is an agent-initiated outdial
|
|
205
|
+
const agentInitiatedOutdial = hasAgentInitiatedOutdial(interaction, agentId);
|
|
206
|
+
|
|
207
|
+
// WebRTC telephony calls
|
|
208
|
+
if (isWebRTCCall(interaction, loginOption, webRtcEnabled)) {
|
|
209
|
+
return autoAnswerEnabled || agentInitiatedOutdial;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Digital outbound (Email/SMS)
|
|
213
|
+
if (isDigitalOutbound(interaction) && agentInitiatedOutdial) {
|
|
214
|
+
// Don't auto-answer if task has been transferred (has previous vteams)
|
|
215
|
+
const hasPreviousVteams = interaction.previousVTeams && interaction.previousVTeams.length > 0;
|
|
216
|
+
|
|
217
|
+
return !hasPreviousVteams;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return false;
|
|
221
|
+
};
|
|
@@ -34,6 +34,8 @@ export const PRESERVED_TASK_DATA_FIELDS = {
|
|
|
34
34
|
WRAP_UP_REQUIRED: 'wrapUpRequired',
|
|
35
35
|
/** Indicates if a conference is currently in progress (2+ active agents) */
|
|
36
36
|
IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress',
|
|
37
|
+
/** Indicates if auto-answer is in progress for this task */
|
|
38
|
+
IS_AUTO_ANSWERING: 'isAutoAnswering',
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -672,6 +672,8 @@ export type Interaction = {
|
|
|
672
672
|
BLIND_TRANSFER_IN_PROGRESS?: boolean;
|
|
673
673
|
/** Desktop view configuration for Flow Control */
|
|
674
674
|
fcDesktopView?: string;
|
|
675
|
+
/** Agent ID who initiated the outdial call */
|
|
676
|
+
outdialAgentId?: string;
|
|
675
677
|
};
|
|
676
678
|
/** Main interaction identifier for related interactions */
|
|
677
679
|
mainInteractionId?: string;
|
|
@@ -797,6 +799,10 @@ export type TaskData = {
|
|
|
797
799
|
reservedAgentChannelId?: string;
|
|
798
800
|
/** Indicates if wrap-up is required for this task */
|
|
799
801
|
wrapUpRequired?: boolean;
|
|
802
|
+
/** Indicates if auto-answer is in progress for this task */
|
|
803
|
+
isAutoAnswering?: boolean;
|
|
804
|
+
/** Indicates if wrap-up is required for this task */
|
|
805
|
+
agentsPendingWrapUp?: string[];
|
|
800
806
|
};
|
|
801
807
|
|
|
802
808
|
/**
|
package/test/unit/spec/cc.ts
CHANGED
|
@@ -152,6 +152,20 @@ describe('metrics/behavioral-events', () => {
|
|
|
152
152
|
verb: 'fail',
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
+
expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS)).toEqual({
|
|
156
|
+
product,
|
|
157
|
+
agent: 'user',
|
|
158
|
+
target: 'task_auto_answer',
|
|
159
|
+
verb: 'complete',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(getEventTaxonomy(METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED)).toEqual({
|
|
163
|
+
product,
|
|
164
|
+
agent: 'user',
|
|
165
|
+
target: 'task_auto_answer',
|
|
166
|
+
verb: 'fail',
|
|
167
|
+
});
|
|
168
|
+
|
|
155
169
|
expect(getEventTaxonomy('' as METRIC_EVENT_NAMES)).toEqual(undefined);
|
|
156
170
|
});
|
|
157
171
|
});
|
|
@@ -736,6 +736,148 @@ describe('TaskManager', () => {
|
|
|
736
736
|
expect(taskEmitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_OUTDIAL_FAILED, 'CUSTOMER_BUSY');
|
|
737
737
|
});
|
|
738
738
|
|
|
739
|
+
it('should handle AGENT_OUTBOUND_FAILED gracefully when task is undefined', () => {
|
|
740
|
+
const payload = {
|
|
741
|
+
data: {
|
|
742
|
+
type: CC_EVENTS.AGENT_OUTBOUND_FAILED,
|
|
743
|
+
interactionId: 'non-existent-task-id',
|
|
744
|
+
reason: 'CUSTOMER_BUSY',
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
// Should not throw error when task doesn't exist
|
|
748
|
+
expect(() => {
|
|
749
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
750
|
+
}).not.toThrow();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('should NOT remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp exists', () => {
|
|
754
|
+
const task = taskManager.getTask(taskId);
|
|
755
|
+
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
756
|
+
task.data = {
|
|
757
|
+
...task.data,
|
|
758
|
+
...newData,
|
|
759
|
+
interaction: {
|
|
760
|
+
...task.data.interaction,
|
|
761
|
+
outboundType: 'OUTDIAL',
|
|
762
|
+
state: 'new',
|
|
763
|
+
mediaType: 'telephony',
|
|
764
|
+
},
|
|
765
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
766
|
+
};
|
|
767
|
+
return task;
|
|
768
|
+
});
|
|
769
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
770
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
771
|
+
|
|
772
|
+
const payload = {
|
|
773
|
+
data: {
|
|
774
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
775
|
+
interactionId: taskId,
|
|
776
|
+
interaction: {
|
|
777
|
+
outboundType: 'OUTDIAL',
|
|
778
|
+
state: 'new',
|
|
779
|
+
mediaType: 'telephony',
|
|
780
|
+
},
|
|
781
|
+
agentsPendingWrapUp: ['agent-123'],
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
786
|
+
|
|
787
|
+
expect(removeTaskSpy).not.toHaveBeenCalled();
|
|
788
|
+
expect(taskManager.getTask(taskId)).toBeDefined();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is empty', () => {
|
|
792
|
+
const task = taskManager.getTask(taskId);
|
|
793
|
+
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
794
|
+
task.data = {
|
|
795
|
+
...task.data,
|
|
796
|
+
...newData,
|
|
797
|
+
interaction: {
|
|
798
|
+
...task.data.interaction,
|
|
799
|
+
outboundType: 'OUTDIAL',
|
|
800
|
+
state: 'new',
|
|
801
|
+
mediaType: 'telephony',
|
|
802
|
+
},
|
|
803
|
+
agentsPendingWrapUp: [],
|
|
804
|
+
};
|
|
805
|
+
return task;
|
|
806
|
+
});
|
|
807
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
808
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
809
|
+
|
|
810
|
+
const payload = {
|
|
811
|
+
data: {
|
|
812
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
813
|
+
interactionId: taskId,
|
|
814
|
+
interaction: {
|
|
815
|
+
outboundType: 'OUTDIAL',
|
|
816
|
+
state: 'new',
|
|
817
|
+
mediaType: 'telephony',
|
|
818
|
+
},
|
|
819
|
+
agentsPendingWrapUp: [],
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
824
|
+
|
|
825
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('should remove OUTDIAL task on CONTACT_ENDED when agentsPendingWrapUp is undefined', () => {
|
|
829
|
+
const task = taskManager.getTask(taskId);
|
|
830
|
+
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
831
|
+
task.data = {
|
|
832
|
+
...task.data,
|
|
833
|
+
...newData,
|
|
834
|
+
interaction: {
|
|
835
|
+
...task.data.interaction,
|
|
836
|
+
outboundType: 'OUTDIAL',
|
|
837
|
+
state: 'new',
|
|
838
|
+
mediaType: 'telephony',
|
|
839
|
+
},
|
|
840
|
+
// agentsPendingWrapUp is undefined
|
|
841
|
+
};
|
|
842
|
+
return task;
|
|
843
|
+
});
|
|
844
|
+
task.unregisterWebCallListeners = jest.fn();
|
|
845
|
+
const removeTaskSpy = jest.spyOn(taskManager, 'removeTaskFromCollection');
|
|
846
|
+
|
|
847
|
+
const payload = {
|
|
848
|
+
data: {
|
|
849
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
850
|
+
interactionId: taskId,
|
|
851
|
+
interaction: {
|
|
852
|
+
outboundType: 'OUTDIAL',
|
|
853
|
+
state: 'new',
|
|
854
|
+
mediaType: 'telephony',
|
|
855
|
+
},
|
|
856
|
+
// agentsPendingWrapUp not included
|
|
857
|
+
},
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
861
|
+
|
|
862
|
+
expect(removeTaskSpy).toHaveBeenCalled();
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('should handle CONTACT_ENDED gracefully when task is undefined', () => {
|
|
866
|
+
const payload = {
|
|
867
|
+
data: {
|
|
868
|
+
type: CC_EVENTS.CONTACT_ENDED,
|
|
869
|
+
interactionId: 'non-existent-task-id',
|
|
870
|
+
interaction: {
|
|
871
|
+
state: 'new',
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
};
|
|
875
|
+
// Should not throw error when task doesn't exist
|
|
876
|
+
expect(() => {
|
|
877
|
+
webSocketManagerMock.emit('message', JSON.stringify(payload));
|
|
878
|
+
}).not.toThrow();
|
|
879
|
+
});
|
|
880
|
+
|
|
739
881
|
it('should remove OUTDIAL task from taskCollection on AGENT_CONTACT_ASSIGN_FAILED when NOT terminated (user-declined)', () => {
|
|
740
882
|
const task = taskManager.getTask(taskId);
|
|
741
883
|
task.updateTaskData = jest.fn().mockImplementation((newData) => {
|
|
@@ -2191,5 +2333,6 @@ describe('TaskManager', () => {
|
|
|
2191
2333
|
);
|
|
2192
2334
|
});
|
|
2193
2335
|
});
|
|
2336
|
+
|
|
2194
2337
|
});
|
|
2195
2338
|
|