@webex/contact-center 3.12.0-llmrefactor.13 → 3.12.0-llmrefactor.15
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/.sdd/manifest.json +3 -2
- package/ai-docs/CONTRACTS.md +7 -1
- package/ai-docs/contact-center-spec.md +7 -7
- package/ai-docs/features/contact-owner-change-refactor.md +78 -0
- package/dist/cc.js +39 -7
- package/dist/cc.js.map +1 -1
- package/dist/metrics/behavioral-events.js +6 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +1 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +4 -4
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +5 -2
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +6 -3
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/task/TaskFactory.js +1 -0
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +50 -58
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +63 -44
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
- package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +52 -11
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +50 -13
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/services/config/types.d.ts +2 -0
- package/dist/types/services/task/types.d.ts +2 -0
- package/dist/types/services/task/voice/Voice.d.ts +15 -2
- package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +19 -6
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +12 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +43 -7
- package/src/metrics/ai-docs/metrics-spec.md +44 -2
- package/src/metrics/behavioral-events.ts +6 -0
- package/src/metrics/constants.ts +1 -0
- package/src/services/AnswerCallOnWebexService.ts +4 -4
- package/src/services/WebexCrossClientService.ts +7 -2
- package/src/services/WxAppTelephonyMercurySync.ts +3 -3
- package/src/services/config/types.ts +2 -0
- package/src/services/task/TaskFactory.ts +1 -0
- package/src/services/task/TaskManager.ts +85 -87
- package/src/services/task/ai-docs/task-spec.md +7 -3
- package/src/services/task/types.ts +16 -0
- package/src/services/task/voice/Voice.ts +74 -52
- package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +83 -24
- package/src/services/wxAppDiagnosticLogging.ts +135 -23
- package/src/types.ts +2 -0
- package/test/unit/spec/cc.ts +201 -19
- package/test/unit/spec/services/WebexCrossClientService.ts +45 -3
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +1 -1
- package/test/unit/spec/services/task/TaskManager.ts +214 -3
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +33 -0
- package/test/unit/spec/services/task/voice/Voice.ts +10 -51
- package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +121 -5
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +44 -8
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -31,16 +31,54 @@ export function callIdSuffix(callId: string | null | undefined): string | undefi
|
|
|
31
31
|
return callId.length <= 8 ? callId : callId.slice(-8);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function
|
|
35
|
-
|
|
34
|
+
function formatLogSuffix(data: WxAppLogData, keys?: string[]): string {
|
|
35
|
+
const entries = keys
|
|
36
|
+
? keys.map((key) => [key, data[key]] as const)
|
|
37
|
+
: Object.entries(data).filter(([key]) => key !== 'feature' && key !== 'event');
|
|
38
|
+
|
|
39
|
+
const parts = entries
|
|
40
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
41
|
+
.map(([key, value]) => `${key}=${String(value)}`);
|
|
42
|
+
|
|
43
|
+
return parts.length > 0 ? ` ${parts.join(' ')}` : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function logWxApp(
|
|
47
|
+
message: string,
|
|
48
|
+
method: string,
|
|
49
|
+
data: WxAppLogData,
|
|
50
|
+
suffixKeys?: string[]
|
|
51
|
+
): void {
|
|
52
|
+
const suffix = formatLogSuffix(data, suffixKeys);
|
|
53
|
+
LoggerProxy.log(`${WXAPP_LOG_PREFIX} ${message}${suffix}`, {
|
|
54
|
+
module: 'wxAppDiagnosticLogging',
|
|
55
|
+
method,
|
|
56
|
+
data: {feature: WXAPP_LOG_FEATURE, ...data},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function logWxAppWarn(
|
|
61
|
+
message: string,
|
|
62
|
+
method: string,
|
|
63
|
+
data: WxAppLogData,
|
|
64
|
+
suffixKeys?: string[]
|
|
65
|
+
): void {
|
|
66
|
+
const suffix = formatLogSuffix(data, suffixKeys);
|
|
67
|
+
LoggerProxy.warn(`${WXAPP_LOG_PREFIX} ${message}${suffix}`, {
|
|
36
68
|
module: 'wxAppDiagnosticLogging',
|
|
37
69
|
method,
|
|
38
70
|
data: {feature: WXAPP_LOG_FEATURE, ...data},
|
|
39
71
|
});
|
|
40
72
|
}
|
|
41
73
|
|
|
42
|
-
function logWxAppError(
|
|
43
|
-
|
|
74
|
+
function logWxAppError(
|
|
75
|
+
message: string,
|
|
76
|
+
method: string,
|
|
77
|
+
data: WxAppLogData,
|
|
78
|
+
suffixKeys?: string[]
|
|
79
|
+
): void {
|
|
80
|
+
const suffix = formatLogSuffix(data, suffixKeys);
|
|
81
|
+
LoggerProxy.error(`${WXAPP_LOG_PREFIX} ${message}${suffix}`, {
|
|
44
82
|
module: 'wxAppDiagnosticLogging',
|
|
45
83
|
method,
|
|
46
84
|
data: {feature: WXAPP_LOG_FEATURE, ...data},
|
|
@@ -56,7 +94,7 @@ export function logWxAppSessionReadiness(params: {
|
|
|
56
94
|
telephonyTaskType: 'Voice' | 'WebRTC' | 'unknown';
|
|
57
95
|
skipReason?: WxAppSessionSkipReason;
|
|
58
96
|
}): void {
|
|
59
|
-
|
|
97
|
+
const data: WxAppLogData = {
|
|
60
98
|
event: 'session_readiness',
|
|
61
99
|
enableWxBetterTogether: params.enableWxBetterTogether,
|
|
62
100
|
loginOption: params.loginOption,
|
|
@@ -65,7 +103,16 @@ export function logWxAppSessionReadiness(params: {
|
|
|
65
103
|
mercurySubscribed: params.mercurySubscribed,
|
|
66
104
|
telephonyTaskType: params.telephonyTaskType,
|
|
67
105
|
skipReason: params.skipReason,
|
|
68
|
-
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
logWxApp('session readiness', 'logWxAppSessionReadiness', data, [
|
|
109
|
+
'usersubPublished',
|
|
110
|
+
'mercurySubscribed',
|
|
111
|
+
'loginOption',
|
|
112
|
+
'enableWxBetterTogether',
|
|
113
|
+
'telephonyTaskType',
|
|
114
|
+
'skipReason',
|
|
115
|
+
]);
|
|
69
116
|
}
|
|
70
117
|
|
|
71
118
|
export function deriveWxAppAcceptReason(params: {
|
|
@@ -111,8 +158,10 @@ export function logWxAppOfferDecision(params: {
|
|
|
111
158
|
acceptReason: WxAppAcceptReason;
|
|
112
159
|
wxAppParticipantDeviceType?: string;
|
|
113
160
|
hasDeviceCallId: boolean;
|
|
161
|
+
hasDeviceId?: boolean;
|
|
162
|
+
usersubPublished?: boolean;
|
|
114
163
|
}): void {
|
|
115
|
-
|
|
164
|
+
const data: WxAppLogData = {
|
|
116
165
|
event: 'offer_decision',
|
|
117
166
|
interactionId: params.interactionId,
|
|
118
167
|
acceptVisible: params.acceptVisible,
|
|
@@ -120,7 +169,51 @@ export function logWxAppOfferDecision(params: {
|
|
|
120
169
|
acceptReason: params.acceptReason,
|
|
121
170
|
wxAppParticipantDeviceType: params.wxAppParticipantDeviceType,
|
|
122
171
|
hasDeviceCallId: params.hasDeviceCallId,
|
|
123
|
-
|
|
172
|
+
hasDeviceId: params.hasDeviceId,
|
|
173
|
+
usersubPublished: params.usersubPublished,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
logWxApp('offer decision', 'logWxAppOfferDecision', data, [
|
|
177
|
+
'acceptReason',
|
|
178
|
+
'hasDeviceCallId',
|
|
179
|
+
'hasDeviceId',
|
|
180
|
+
'usersubPublished',
|
|
181
|
+
'interactionId',
|
|
182
|
+
'acceptVisible',
|
|
183
|
+
'acceptEnabled',
|
|
184
|
+
]);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function logWxAppOfferParticipantMismatch(params: {
|
|
188
|
+
interactionId: string;
|
|
189
|
+
usersubPublished: boolean;
|
|
190
|
+
hasDeviceCallId: boolean;
|
|
191
|
+
hasDeviceId: boolean;
|
|
192
|
+
participantDeviceType?: string;
|
|
193
|
+
acceptVisible: boolean;
|
|
194
|
+
acceptEnabled: boolean;
|
|
195
|
+
acceptReason: WxAppAcceptReason;
|
|
196
|
+
}): void {
|
|
197
|
+
const data: WxAppLogData = {
|
|
198
|
+
event: 'participant_fields_mismatch',
|
|
199
|
+
interactionId: params.interactionId,
|
|
200
|
+
usersubPublished: params.usersubPublished,
|
|
201
|
+
hasDeviceCallId: params.hasDeviceCallId,
|
|
202
|
+
hasDeviceId: params.hasDeviceId,
|
|
203
|
+
participantDeviceType: params.participantDeviceType ?? 'missing',
|
|
204
|
+
acceptVisible: params.acceptVisible,
|
|
205
|
+
acceptEnabled: params.acceptEnabled,
|
|
206
|
+
acceptReason: params.acceptReason,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
logWxAppWarn('participant fields mismatch', 'logWxAppOfferParticipantMismatch', data, [
|
|
210
|
+
'usersubPublished',
|
|
211
|
+
'hasDeviceCallId',
|
|
212
|
+
'hasDeviceId',
|
|
213
|
+
'acceptReason',
|
|
214
|
+
'interactionId',
|
|
215
|
+
'participantDeviceType',
|
|
216
|
+
]);
|
|
124
217
|
}
|
|
125
218
|
|
|
126
219
|
export function logWxAppTelephonyAction(params: {
|
|
@@ -142,18 +235,32 @@ export function logWxAppTelephonyAction(params: {
|
|
|
142
235
|
};
|
|
143
236
|
|
|
144
237
|
if (params.phase === 'failed') {
|
|
145
|
-
logWxAppError(
|
|
238
|
+
logWxAppError(
|
|
239
|
+
`telephony ${params.action} ${params.phase}`,
|
|
240
|
+
'logWxAppTelephonyAction',
|
|
241
|
+
payload,
|
|
242
|
+
['interactionId', 'trackingId', 'httpStatus', 'failureReason']
|
|
243
|
+
);
|
|
146
244
|
} else {
|
|
147
|
-
logWxApp(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload
|
|
245
|
+
logWxApp(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload, [
|
|
246
|
+
'interactionId',
|
|
247
|
+
'action',
|
|
248
|
+
'phase',
|
|
249
|
+
]);
|
|
148
250
|
}
|
|
149
251
|
}
|
|
150
252
|
|
|
151
253
|
export function logWxAppValidationFailure(reason: string, interactionId?: string): void {
|
|
152
|
-
logWxAppError(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
254
|
+
logWxAppError(
|
|
255
|
+
'validation failed',
|
|
256
|
+
'logWxAppValidationFailure',
|
|
257
|
+
{
|
|
258
|
+
event: 'validation_failed',
|
|
259
|
+
reason,
|
|
260
|
+
interactionId,
|
|
261
|
+
},
|
|
262
|
+
['reason', 'interactionId']
|
|
263
|
+
);
|
|
157
264
|
}
|
|
158
265
|
|
|
159
266
|
export function logWxAppMercuryMuteSync(params: {
|
|
@@ -163,12 +270,17 @@ export function logWxAppMercuryMuteSync(params: {
|
|
|
163
270
|
interactionId?: string;
|
|
164
271
|
dropReason?: string;
|
|
165
272
|
}): void {
|
|
166
|
-
logWxApp(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
273
|
+
logWxApp(
|
|
274
|
+
`mercury mute sync ${params.phase}`,
|
|
275
|
+
'logWxAppMercuryMuteSync',
|
|
276
|
+
{
|
|
277
|
+
event: 'mercury_mute_sync',
|
|
278
|
+
phase: params.phase,
|
|
279
|
+
muted: params.muted,
|
|
280
|
+
callIdSuffix: params.callIdSuffix,
|
|
281
|
+
interactionId: params.interactionId,
|
|
282
|
+
dropReason: params.dropReason,
|
|
283
|
+
},
|
|
284
|
+
['phase', 'muted', 'callIdSuffix', 'interactionId', 'dropReason']
|
|
285
|
+
);
|
|
174
286
|
}
|
package/src/types.ts
CHANGED
|
@@ -618,6 +618,8 @@ export type ConfigFlags = {
|
|
|
618
618
|
isRecordingEnabled?: boolean;
|
|
619
619
|
/** Whether wxApp thick-client answer controls/APIs are enabled (WXCC-6026) */
|
|
620
620
|
enableWxBetterTogether?: boolean;
|
|
621
|
+
/** Read-only observability: live usersub answer-calls-on-wxcc state for wxApp offer diagnostics. */
|
|
622
|
+
getWxAppUsersubPublished?: () => boolean;
|
|
621
623
|
};
|
|
622
624
|
|
|
623
625
|
/**
|
package/test/unit/spec/cc.ts
CHANGED
|
@@ -171,12 +171,14 @@ describe('webex.cc', () => {
|
|
|
171
171
|
setAgentId: jest.fn(),
|
|
172
172
|
setWebRtcEnabled: jest.fn(),
|
|
173
173
|
setAnswerCallOnWebexService: jest.fn(),
|
|
174
|
+
handleRealtimeWebsocketEvent: jest.fn(),
|
|
174
175
|
registerIncomingCallEvent: jest.fn(),
|
|
175
176
|
registerTaskListeners: jest.fn(),
|
|
176
177
|
getTask: jest.fn(),
|
|
177
178
|
getActiveTasks: jest.fn(),
|
|
178
179
|
applyEnableWxBetterTogether: jest.fn(),
|
|
179
180
|
syncWxAppMuteFromCallDetailsForAllTasks: jest.fn(),
|
|
181
|
+
refreshWxAppOfferObservabilityForAllTasks: jest.fn(),
|
|
180
182
|
on: jest.fn(),
|
|
181
183
|
off: jest.fn(),
|
|
182
184
|
emit: jest.fn(),
|
|
@@ -334,6 +336,7 @@ describe('webex.cc', () => {
|
|
|
334
336
|
data: {
|
|
335
337
|
auxCodeId: 'auxCodeId',
|
|
336
338
|
agentId: 'agentId',
|
|
339
|
+
agentSessionId: 'session-after-refresh',
|
|
337
340
|
deviceType: LoginOption.EXTENSION,
|
|
338
341
|
dn: '12345',
|
|
339
342
|
},
|
|
@@ -406,22 +409,25 @@ describe('webex.cc', () => {
|
|
|
406
409
|
module: CC_FILE,
|
|
407
410
|
method: 'connectWebsocket',
|
|
408
411
|
});
|
|
409
|
-
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
412
|
+
expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith(
|
|
413
|
+
expect.objectContaining({
|
|
414
|
+
isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
|
|
415
|
+
isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
|
|
416
|
+
webRtcEnabled: mockAgentProfile.webRtcEnabled,
|
|
417
|
+
autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
|
|
418
|
+
aiFeature: mockAgentProfile.aiFeature,
|
|
419
|
+
consultTransfer: {
|
|
420
|
+
allowConsultToQueue: mockAgentProfile.allowConsultToQueue,
|
|
421
|
+
accessQueue: mockAgentProfile.accessQueue,
|
|
422
|
+
accessEntryPoint: mockAgentProfile.accessEntryPoint,
|
|
423
|
+
accessBuddyTeam: mockAgentProfile.accessBuddyTeam,
|
|
424
|
+
},
|
|
425
|
+
enableWxBetterTogether: false,
|
|
426
|
+
getWxAppUsersubPublished: expect.any(Function),
|
|
427
|
+
})
|
|
428
|
+
);
|
|
423
429
|
expect(reloadSpy).toHaveBeenCalled();
|
|
424
|
-
expect(result).toEqual(mockAgentProfile);
|
|
430
|
+
expect(result).toEqual({...mockAgentProfile, agentSessionId: 'session-after-refresh'});
|
|
425
431
|
expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
|
|
426
432
|
METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS,
|
|
427
433
|
METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
|
|
@@ -653,8 +659,47 @@ describe('webex.cc', () => {
|
|
|
653
659
|
expect(result).toEqual(mockAgentProfile);
|
|
654
660
|
});
|
|
655
661
|
|
|
656
|
-
it('should
|
|
657
|
-
mockAgentProfile.aiFeature = {
|
|
662
|
+
it('should connect RTD websocket when only wellness breaks are enabled', async () => {
|
|
663
|
+
mockAgentProfile.aiFeature = {
|
|
664
|
+
realtimeTranscripts: {enable: false},
|
|
665
|
+
suggestedResponses: {enable: false},
|
|
666
|
+
} as any;
|
|
667
|
+
mockAgentProfile.isWellnessBreakEnabled = true;
|
|
668
|
+
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
669
|
+
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
670
|
+
data: {
|
|
671
|
+
auxCodeId: 'auxCodeId',
|
|
672
|
+
agentId: 'agentId',
|
|
673
|
+
deviceType: LoginOption.EXTENSION,
|
|
674
|
+
dn: '12345',
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
|
|
678
|
+
mockWebSocketManager.initWebSocket.mockResolvedValue({agentId: 'agent123'});
|
|
679
|
+
|
|
680
|
+
await webex.cc.register();
|
|
681
|
+
|
|
682
|
+
expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
|
|
683
|
+
body: {
|
|
684
|
+
force: true,
|
|
685
|
+
isKeepAliveEnabled: false,
|
|
686
|
+
clientType: 'WebexCCSDK',
|
|
687
|
+
allowMultiLogin: false,
|
|
688
|
+
},
|
|
689
|
+
resource: 'v1/realtime/subscribe',
|
|
690
|
+
});
|
|
691
|
+
expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
|
|
692
|
+
'message',
|
|
693
|
+
expect.any(Function)
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('should not connect RTD websocket when transcripts, suggestions, and wellness are disabled', async () => {
|
|
698
|
+
mockAgentProfile.aiFeature = {
|
|
699
|
+
realtimeTranscripts: {enable: false},
|
|
700
|
+
suggestedResponses: {enable: false},
|
|
701
|
+
} as any;
|
|
702
|
+
mockAgentProfile.isWellnessBreakEnabled = false;
|
|
658
703
|
jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
|
|
659
704
|
jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
|
|
660
705
|
data: {
|
|
@@ -1097,6 +1142,7 @@ describe('webex.cc', () => {
|
|
|
1097
1142
|
|
|
1098
1143
|
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
1099
1144
|
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
1145
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
1100
1146
|
});
|
|
1101
1147
|
|
|
1102
1148
|
it('should rollback wxApp config when post-station-login init fails', async () => {
|
|
@@ -1227,7 +1273,57 @@ describe('webex.cc', () => {
|
|
|
1227
1273
|
|
|
1228
1274
|
expect(metricSpy).toHaveBeenCalledWith(
|
|
1229
1275
|
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1230
|
-
expect.objectContaining({skipReason: 'publish_failed'}),
|
|
1276
|
+
expect.objectContaining({skipReason: 'publish_failed', usersubPublished: false}),
|
|
1277
|
+
['operational', 'behavioral']
|
|
1278
|
+
);
|
|
1279
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('should report retained usersub state when publish fails during re-init', async () => {
|
|
1283
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1284
|
+
webex.internal.device = {userId: 'user-123', url: 'https://wdm.example.com/devices/dev-1'};
|
|
1285
|
+
webex.cc.agentConfig = {
|
|
1286
|
+
agentId: 'agentId',
|
|
1287
|
+
webRtcEnabled: false,
|
|
1288
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1289
|
+
};
|
|
1290
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1291
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1292
|
+
|
|
1293
|
+
jest.spyOn(webex.cc as any, 'ensureWxAppMercuryConnected').mockResolvedValue(undefined);
|
|
1294
|
+
jest
|
|
1295
|
+
.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'subscribe')
|
|
1296
|
+
.mockImplementation(() => {});
|
|
1297
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1298
|
+
jest
|
|
1299
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1300
|
+
.mockReturnValue(true);
|
|
1301
|
+
jest
|
|
1302
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1303
|
+
.mockRejectedValue(new Error('publish failed'));
|
|
1304
|
+
jest
|
|
1305
|
+
.spyOn(webex.cc as any, 'releaseWxAppMercuryResources')
|
|
1306
|
+
.mockResolvedValue(undefined);
|
|
1307
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1308
|
+
|
|
1309
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1310
|
+
data: {
|
|
1311
|
+
agentId: 'agentId',
|
|
1312
|
+
teamId: 'teamId',
|
|
1313
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1314
|
+
},
|
|
1315
|
+
trackingId: 'track-1',
|
|
1316
|
+
} as StationLoginSuccess);
|
|
1317
|
+
|
|
1318
|
+
await webex.cc.stationLogin({
|
|
1319
|
+
teamId: 'teamId',
|
|
1320
|
+
loginOption: LoginOption.EXTENSION,
|
|
1321
|
+
dialNumber: '1001',
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1325
|
+
METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
|
|
1326
|
+
expect.objectContaining({skipReason: 'publish_failed', usersubPublished: true}),
|
|
1231
1327
|
['operational', 'behavioral']
|
|
1232
1328
|
);
|
|
1233
1329
|
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(false);
|
|
@@ -1639,7 +1735,61 @@ describe('webex.cc', () => {
|
|
|
1639
1735
|
);
|
|
1640
1736
|
expect(metricSpy).toHaveBeenCalledWith(
|
|
1641
1737
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
1642
|
-
expect.objectContaining({skipReason: 'user_id_unavailable'}),
|
|
1738
|
+
expect.objectContaining({skipReason: 'user_id_unavailable', usersubPublished: false}),
|
|
1739
|
+
['operational', 'behavioral']
|
|
1740
|
+
);
|
|
1741
|
+
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it('should report retained usersub state when userId is missing during re-init', async () => {
|
|
1745
|
+
webex.cc.$config = {...webex.cc.$config, enableWxBetterTogether: true};
|
|
1746
|
+
webex.internal.device = {
|
|
1747
|
+
url: 'https://wdm.example.com/devices/dev-1',
|
|
1748
|
+
registered: true,
|
|
1749
|
+
register: jest.fn().mockResolvedValue(undefined),
|
|
1750
|
+
};
|
|
1751
|
+
webex.internal.mercury = {
|
|
1752
|
+
connected: true,
|
|
1753
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
1754
|
+
on: jest.fn(),
|
|
1755
|
+
off: jest.fn(),
|
|
1756
|
+
};
|
|
1757
|
+
webex.cc.agentConfig = {
|
|
1758
|
+
agentId: 'agentId',
|
|
1759
|
+
webRtcEnabled: false,
|
|
1760
|
+
loginVoiceOptions: ['EXTENSION'],
|
|
1761
|
+
};
|
|
1762
|
+
webex.cc.webCallingService.loginOption = LoginOption.EXTENSION;
|
|
1763
|
+
mockTaskManager.applyEnableWxBetterTogether(true);
|
|
1764
|
+
|
|
1765
|
+
jest
|
|
1766
|
+
.spyOn(webex.cc['webexCrossClientService'], 'isAnswerCallsStateActive')
|
|
1767
|
+
.mockReturnValue(true);
|
|
1768
|
+
const publishSpy = jest
|
|
1769
|
+
.spyOn(webex.cc['webexCrossClientService'], 'setManageWebexCallingInWxcc')
|
|
1770
|
+
.mockResolvedValue(undefined);
|
|
1771
|
+
jest.spyOn(webex.cc['wxAppTelephonyMercurySync'], 'isSubscribed').mockReturnValue(true);
|
|
1772
|
+
const metricSpy = jest.spyOn(mockMetricsManager, 'trackEvent');
|
|
1773
|
+
|
|
1774
|
+
jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
|
|
1775
|
+
data: {
|
|
1776
|
+
agentId: 'agentId',
|
|
1777
|
+
teamId: 'teamId',
|
|
1778
|
+
channelsMap: {chat: [], email: [], social: [], telephony: []},
|
|
1779
|
+
},
|
|
1780
|
+
trackingId: 'track-1',
|
|
1781
|
+
} as StationLoginSuccess);
|
|
1782
|
+
|
|
1783
|
+
await webex.cc.stationLogin({
|
|
1784
|
+
teamId: 'teamId',
|
|
1785
|
+
loginOption: LoginOption.EXTENSION,
|
|
1786
|
+
dialNumber: '1001',
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
expect(publishSpy).not.toHaveBeenCalledWith(true, expect.anything());
|
|
1790
|
+
expect(metricSpy).toHaveBeenCalledWith(
|
|
1791
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
1792
|
+
expect.objectContaining({skipReason: 'user_id_unavailable', usersubPublished: true}),
|
|
1643
1793
|
['operational', 'behavioral']
|
|
1644
1794
|
);
|
|
1645
1795
|
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
@@ -2213,6 +2363,36 @@ describe('webex.cc', () => {
|
|
|
2213
2363
|
});
|
|
2214
2364
|
});
|
|
2215
2365
|
|
|
2366
|
+
it('routes data-socket wellness notifications without sending them to TaskManager', () => {
|
|
2367
|
+
const emitSpy = jest.spyOn(webex.cc, 'emit');
|
|
2368
|
+
const taskEventSpy = mockTaskManager.handleRealtimeWebsocketEvent;
|
|
2369
|
+
const wellnessMessage = JSON.stringify({
|
|
2370
|
+
type: 'Wellness_Break_Handler',
|
|
2371
|
+
orgId: 'mockOrgId',
|
|
2372
|
+
data: {
|
|
2373
|
+
agentId: 'agent-1',
|
|
2374
|
+
orgId: 'mockOrgId',
|
|
2375
|
+
notifType: 'Wellness_Break_Handler',
|
|
2376
|
+
notifDetails: {actionEvent: 'PROVIDE_WELLNESS_BREAK'},
|
|
2377
|
+
data: {orgId: 'mockOrgId', agentSessionId: 'notification-session'},
|
|
2378
|
+
},
|
|
2379
|
+
});
|
|
2380
|
+
|
|
2381
|
+
webex.cc['handleRTDWebsocketMessage'](wellnessMessage);
|
|
2382
|
+
|
|
2383
|
+
expect(emitSpy).toHaveBeenCalledWith(CC_EVENTS.WELLNESS_BREAK, {
|
|
2384
|
+
agentId: 'agent-1',
|
|
2385
|
+
orgId: 'mockOrgId',
|
|
2386
|
+
agentSessionId: 'notification-session',
|
|
2387
|
+
actionEvent: 'PROVIDE_WELLNESS_BREAK',
|
|
2388
|
+
});
|
|
2389
|
+
expect(taskEventSpy).not.toHaveBeenCalled();
|
|
2390
|
+
|
|
2391
|
+
const transcriptMessage = JSON.stringify({type: CC_EVENTS.REAL_TIME_TRANSCRIPTION});
|
|
2392
|
+
webex.cc['handleRTDWebsocketMessage'](transcriptMessage);
|
|
2393
|
+
expect(taskEventSpy).toHaveBeenCalledWith(transcriptMessage);
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2216
2396
|
it.each([
|
|
2217
2397
|
['SUGGEST_WELLNESS_BREAK', 'interactionId'],
|
|
2218
2398
|
['WELLNESS_BREAK_NOT_ALLOWED', 'InteractionId'],
|
|
@@ -2678,6 +2858,7 @@ describe('webex.cc', () => {
|
|
|
2678
2858
|
|
|
2679
2859
|
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
2680
2860
|
expect(subscribeSpy).toHaveBeenCalledWith('agentId', expect.any(Function));
|
|
2861
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
2681
2862
|
});
|
|
2682
2863
|
|
|
2683
2864
|
it('should force-publish usersub false on silent relogin when enableWxBetterTogether is false at init', async () => {
|
|
@@ -4140,6 +4321,7 @@ describe('webex.cc', () => {
|
|
|
4140
4321
|
expect(webex.cc.isWxBetterTogetherEnabled()).toBe(true);
|
|
4141
4322
|
expect(mockTaskManager.applyEnableWxBetterTogether).toHaveBeenCalledWith(true);
|
|
4142
4323
|
expect(publishSpy).toHaveBeenCalledWith(true, {userId: 'user-123', trackPublishMetrics: true});
|
|
4324
|
+
expect(mockTaskManager.refreshWxAppOfferObservabilityForAllTasks).toHaveBeenCalled();
|
|
4143
4325
|
});
|
|
4144
4326
|
|
|
4145
4327
|
it('should publish false and update config when disabled after wxApp was enabled', async () => {
|
|
@@ -160,11 +160,53 @@ describe('WebexCrossClientService', () => {
|
|
|
160
160
|
|
|
161
161
|
expect(trackEvent).toHaveBeenCalledWith(
|
|
162
162
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
163
|
-
expect.objectContaining({enableWxBetterTogether: true}),
|
|
163
|
+
expect.objectContaining({enableWxBetterTogether: true, usersubPublished: false}),
|
|
164
164
|
['operational', 'behavioral']
|
|
165
165
|
);
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
+
it('reports retained usersub state when disable publish fails after successful enable', async () => {
|
|
169
|
+
await service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
170
|
+
trackEvent.mockClear();
|
|
171
|
+
|
|
172
|
+
webex.request = jest.fn().mockRejectedValue(new Error('disable failed'));
|
|
173
|
+
|
|
174
|
+
await expect(
|
|
175
|
+
service.setManageWebexCallingInWxcc(false, {trackPublishMetrics: true})
|
|
176
|
+
).rejects.toThrow('disable failed');
|
|
177
|
+
|
|
178
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
179
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
enableWxBetterTogether: false,
|
|
182
|
+
usersubPublished: true,
|
|
183
|
+
}),
|
|
184
|
+
['operational', 'behavioral']
|
|
185
|
+
);
|
|
186
|
+
expect(service.isAnswerCallsStateActive()).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('reports retained usersub state when re-enable publish fails after successful enable', async () => {
|
|
190
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
191
|
+
trackEvent.mockClear();
|
|
192
|
+
|
|
193
|
+
webex.request = jest.fn().mockRejectedValue(new Error('refresh failed'));
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
197
|
+
).rejects.toThrow('refresh failed');
|
|
198
|
+
|
|
199
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
200
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
201
|
+
expect.objectContaining({
|
|
202
|
+
enableWxBetterTogether: true,
|
|
203
|
+
usersubPublished: true,
|
|
204
|
+
}),
|
|
205
|
+
['operational', 'behavioral']
|
|
206
|
+
);
|
|
207
|
+
expect(service.isAnswerCallsStateActive()).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
168
210
|
it('tracks usersub publish success when trackPublishMetrics is enabled', async () => {
|
|
169
211
|
await service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
170
212
|
|
|
@@ -174,7 +216,7 @@ describe('WebexCrossClientService', () => {
|
|
|
174
216
|
]);
|
|
175
217
|
expect(trackEvent).toHaveBeenCalledWith(
|
|
176
218
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
177
|
-
{enableWxBetterTogether: true},
|
|
219
|
+
{enableWxBetterTogether: true, usersubPublished: true},
|
|
178
220
|
['operational', 'behavioral']
|
|
179
221
|
);
|
|
180
222
|
});
|
|
@@ -243,7 +285,7 @@ describe('WebexCrossClientService', () => {
|
|
|
243
285
|
expect(cancelTimedEvent).not.toHaveBeenCalled();
|
|
244
286
|
expect(trackEvent).toHaveBeenCalledWith(
|
|
245
287
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
246
|
-
{enableWxBetterTogether: false},
|
|
288
|
+
{enableWxBetterTogether: false, usersubPublished: false},
|
|
247
289
|
['operational', 'behavioral']
|
|
248
290
|
);
|
|
249
291
|
});
|
|
@@ -42,7 +42,7 @@ describe('WxAppTelephonyMercurySync', () => {
|
|
|
42
42
|
expect(sync.isSubscribed()).toBe(true);
|
|
43
43
|
expect(trackEvent).toHaveBeenCalledWith(
|
|
44
44
|
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_SUCCESS,
|
|
45
|
-
{},
|
|
45
|
+
{mercurySubscribed: true},
|
|
46
46
|
['operational', 'behavioral']
|
|
47
47
|
);
|
|
48
48
|
});
|